| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use fuchsia_zircon::{self as zx, sys::zx_thread_state_general_regs_t, AsHandleRef, HandleBased}; |
| use process_builder::{elf_load, elf_parse}; |
| use std::ffi::{CStr, CString}; |
| use std::sync::Arc; |
| use zerocopy::AsBytes; |
| |
| use crate::fs::FileHandle; |
| use crate::logging::*; |
| use crate::mm::*; |
| use crate::task::*; |
| use crate::types::*; |
| |
| fn populate_initial_stack( |
| stack_vmo: &zx::Vmo, |
| path: &CStr, |
| argv: &Vec<CString>, |
| environ: &Vec<CString>, |
| mut auxv: Vec<(u32, u64)>, |
| stack_base: UserAddress, |
| original_stack_start_addr: UserAddress, |
| ) -> Result<UserAddress, Errno> { |
| let mut stack_pointer = original_stack_start_addr; |
| let write_stack = |data: &[u8], addr: UserAddress| { |
| stack_vmo |
| .write(data, (addr - stack_base) as u64) |
| .map_err(|status| from_status_like_fdio!(status)) |
| }; |
| |
| let mut string_data = vec![]; |
| for arg in argv { |
| string_data.extend_from_slice(arg.as_bytes_with_nul()); |
| } |
| for env in environ { |
| string_data.extend_from_slice(env.as_bytes_with_nul()); |
| } |
| stack_pointer -= string_data.len(); |
| let strings_addr = stack_pointer; |
| write_stack(string_data.as_slice(), strings_addr)?; |
| |
| // Write the path used with execve. |
| stack_pointer -= path.to_bytes_with_nul().len(); |
| let execfn_addr = stack_pointer; |
| write_stack(path.to_bytes_with_nul(), execfn_addr)?; |
| |
| let mut random_seed = [0; 16]; |
| zx::cprng_draw(&mut random_seed); |
| stack_pointer -= random_seed.len(); |
| let random_seed_addr = stack_pointer; |
| write_stack(&random_seed, random_seed_addr)?; |
| |
| auxv.push((AT_EXECFN, execfn_addr.ptr() as u64)); |
| auxv.push((AT_RANDOM, random_seed_addr.ptr() as u64)); |
| auxv.push((AT_NULL, 0)); |
| |
| // After the remainder (argc/argv/environ/auxv) is pushed, the stack pointer must be 16 byte |
| // aligned. This is required by the ABI and assumed by the compiler to correctly align SSE |
| // operations. But this can't be done after it's pushed, since it has to be right at the top of |
| // the stack. So we collect it all, align the stack appropriately now that we know the size, |
| // and push it all at once. |
| let mut main_data = vec![]; |
| // argc |
| let argc: u64 = argv.len() as u64; |
| main_data.extend_from_slice(&argc.to_ne_bytes()); |
| // argv |
| const ZERO: [u8; 8] = [0; 8]; |
| let mut next_string_addr = strings_addr; |
| for arg in argv { |
| main_data.extend_from_slice(&next_string_addr.ptr().to_ne_bytes()); |
| next_string_addr += arg.as_bytes_with_nul().len(); |
| } |
| main_data.extend_from_slice(&ZERO); |
| // environ |
| for env in environ { |
| main_data.extend_from_slice(&next_string_addr.ptr().to_ne_bytes()); |
| next_string_addr += env.as_bytes_with_nul().len(); |
| } |
| main_data.extend_from_slice(&ZERO); |
| // auxv |
| for (tag, val) in auxv { |
| main_data.extend_from_slice(&(tag as u64).to_ne_bytes()); |
| main_data.extend_from_slice(&(val as u64).to_ne_bytes()); |
| } |
| |
| // Time to push. |
| stack_pointer -= main_data.len(); |
| stack_pointer -= stack_pointer.ptr() % 16; |
| write_stack(main_data.as_slice(), stack_pointer)?; |
| |
| return Ok(stack_pointer); |
| } |
| |
| struct LoadedElf { |
| headers: elf_parse::Elf64Headers, |
| file_base: usize, |
| vaddr_bias: usize, |
| vmo: zx::Vmo, |
| } |
| |
| // TODO: Improve the error reporting produced by this function by mapping ElfParseError to Errno more precisely. |
| fn elf_parse_error_to_errno(err: elf_parse::ElfParseError) -> Errno { |
| tracing::warn!("elf parse error: {:?}", err); |
| errno!(EINVAL) |
| } |
| |
| // TODO: Improve the error reporting produced by this function by mapping ElfLoadError to Errno more precisely. |
| fn elf_load_error_to_errno(err: elf_load::ElfLoadError) -> Errno { |
| tracing::warn!("elf load error: {:?}", err); |
| errno!(EINVAL) |
| } |
| |
| struct Mapper<'a> { |
| file: &'a FileHandle, |
| mm: &'a MemoryManager, |
| } |
| impl elf_load::Mapper for Mapper<'_> { |
| fn map( |
| &self, |
| vmar_offset: usize, |
| vmo: &zx::Vmo, |
| vmo_offset: u64, |
| length: usize, |
| flags: zx::VmarFlags, |
| ) -> Result<usize, zx::Status> { |
| let vmo = Arc::new(vmo.duplicate_handle(zx::Rights::SAME_RIGHTS)?); |
| self.mm |
| .map( |
| DesiredAddress::Fixed(self.mm.base_addr + vmar_offset), |
| vmo, |
| vmo_offset, |
| length, |
| flags, |
| MappingOptions::empty(), |
| Some(self.file.name.clone()), |
| ) |
| .map_err(|e| { |
| // TODO: Find a way to propagate this errno to the caller. |
| tracing::error!("elf map error: {:?}", e); |
| zx::Status::INVALID_ARGS |
| }) |
| .map(|addr| addr.ptr()) |
| } |
| } |
| |
| fn load_elf(elf: FileHandle, elf_vmo: zx::Vmo, mm: &MemoryManager) -> Result<LoadedElf, Errno> { |
| let headers = elf_parse::Elf64Headers::from_vmo(&elf_vmo).map_err(elf_parse_error_to_errno)?; |
| let elf_info = elf_load::loaded_elf_info(&headers); |
| let file_base = match headers.file_header().elf_type() { |
| Ok(elf_parse::ElfType::SharedObject) => { |
| mm.get_random_base(elf_info.high - elf_info.low).ptr() |
| } |
| Ok(elf_parse::ElfType::Executable) => elf_info.low, |
| _ => return error!(EINVAL), |
| }; |
| let vaddr_bias = file_base.wrapping_sub(elf_info.low); |
| let mapper = Mapper { file: &elf, mm }; |
| elf_load::map_elf_segments(&elf_vmo, &headers, &mapper, mm.base_addr.ptr(), vaddr_bias) |
| .map_err(elf_load_error_to_errno)?; |
| Ok(LoadedElf { headers, file_base, vaddr_bias, vmo: elf_vmo }) |
| } |
| |
| pub struct ThreadStartInfo { |
| pub entry: UserAddress, |
| pub stack: UserAddress, |
| |
| /// The address of the DT_DEBUG entry. |
| pub dt_debug_address: Option<UserAddress>, |
| } |
| |
| impl ThreadStartInfo { |
| pub fn to_registers(&self) -> zx_thread_state_general_regs_t { |
| let mut registers = zx_thread_state_general_regs_t::default(); |
| registers.rip = self.entry.ptr() as u64; |
| registers.rsp = self.stack.ptr() as u64; |
| registers |
| } |
| } |
| |
| /// Holds a resolved ELF VMO and associated parameters necessary for an execve call. |
| pub struct ResolvedElf { |
| /// A file handle to the resolved ELF executable. |
| pub file: FileHandle, |
| /// A VMO to the resolved ELF executable. |
| pub vmo: zx::Vmo, |
| /// An ELF interpreter, if specified in the ELF executable header. |
| pub interp: Option<ResolvedInterpElf>, |
| /// Arguments to be passed to the new process. |
| pub argv: Vec<CString>, |
| /// The environment to initialize for the new process. |
| pub environ: Vec<CString>, |
| } |
| |
| /// Holds a resolved ELF interpreter VMO. |
| pub struct ResolvedInterpElf { |
| /// A file handle to the resolved ELF interpreter. |
| file: FileHandle, |
| /// A VMO to the resolved ELF interpreter. |
| vmo: zx::Vmo, |
| } |
| |
| // The magic bytes of a script file. |
| const HASH_BANG: &[u8; 2] = b"#!"; |
| const MAX_RECURSION_DEPTH: usize = 4; |
| |
| /// Resolves a file into a validated executable ELF, following script interpreters to a fixed |
| /// recursion depth. `argv` may change due to script interpreter logic. |
| pub fn resolve_executable( |
| current_task: &CurrentTask, |
| file: FileHandle, |
| path: CString, |
| argv: Vec<CString>, |
| environ: Vec<CString>, |
| ) -> Result<ResolvedElf, Errno> { |
| resolve_executable_impl(current_task, file, path, argv, environ, 0) |
| } |
| |
| /// Resolves a file into a validated executable ELF, following script interpreters to a fixed |
| /// recursion depth. |
| fn resolve_executable_impl( |
| current_task: &CurrentTask, |
| file: FileHandle, |
| path: CString, |
| argv: Vec<CString>, |
| environ: Vec<CString>, |
| recursion_depth: usize, |
| ) -> Result<ResolvedElf, Errno> { |
| if recursion_depth >= MAX_RECURSION_DEPTH { |
| return error!(ELOOP); |
| } |
| let vmo = |
| file.get_vmo(current_task, None, zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_EXECUTE)?; |
| let mut header = [0u8; 2]; |
| match vmo.read(&mut header, 0) { |
| Ok(()) => {} |
| Err(zx::Status::OUT_OF_RANGE) => { |
| // The file is empty, or it would have at least one page allocated to it. |
| return error!(ENOEXEC); |
| } |
| Err(_) => return error!(EINVAL), |
| } |
| if &header == HASH_BANG { |
| resolve_script(current_task, vmo, path, argv, environ, recursion_depth) |
| } else { |
| resolve_elf(current_task, file, vmo, argv, environ) |
| } |
| } |
| |
| /// Resolves a #! script file into a validated executable ELF. |
| fn resolve_script( |
| current_task: &CurrentTask, |
| vmo: zx::Vmo, |
| path: CString, |
| argv: Vec<CString>, |
| environ: Vec<CString>, |
| recursion_depth: usize, |
| ) -> Result<ResolvedElf, Errno> { |
| // All VMOs have sizes in multiple of the system page size, so as long as we only read a page or |
| // less, we should never read past the end of the VMO. |
| // Since Linux 5.1, the max length of the interpreter following the #! is 255. |
| const HEADER_BUFFER_CAP: usize = 255 + HASH_BANG.len(); |
| let mut buffer = [0u8; HEADER_BUFFER_CAP]; |
| vmo.read(&mut buffer, 0).map_err(|_| errno!(EINVAL))?; |
| |
| let mut args = parse_interpreter_line(&buffer)?; |
| let interpreter = current_task.open_file(args[0].as_bytes(), OpenFlags::RDONLY)?; |
| |
| // Append the original script executable path as an argument. |
| args.push(path); |
| |
| // Append the original arguments (minus argv[0]). |
| args.extend(argv.into_iter().skip(1)); |
| |
| // Recurse and resolve the interpreter executable |
| resolve_executable_impl( |
| current_task, |
| interpreter, |
| args[0].clone(), |
| args, |
| environ, |
| recursion_depth + 1, |
| ) |
| } |
| |
| /// Parses a "#!" byte string and extracts CString arguments. The byte string must contain an |
| /// ASCII newline character or null-byte, or else it is considered truncated and parsing will fail. |
| /// If the byte string is empty or contains only whitespace, parsing fails. |
| /// If successful, the returned `Vec` will have at least one element (the interpreter path). |
| fn parse_interpreter_line(line: &[u8]) -> Result<Vec<CString>, Errno> { |
| // Assuming the byte string starts with "#!", truncate the input to end at the first newline or |
| // null-byte. If not found, assume the input was truncated and fail parsing. |
| let end = line.iter().position(|&b| b == b'\n' || b == 0).ok_or_else(|| errno!(EINVAL))?; |
| let line = &line[HASH_BANG.len()..end]; |
| |
| // Split the byte string at the first whitespace character (or end of line). The first part |
| // is the interpreter path. |
| let is_tab_or_space = |&b| b == b' ' || b == b'\t'; |
| let first_whitespace = line.iter().position(is_tab_or_space).unwrap_or(line.len()); |
| let (interpreter, rest) = line.split_at(first_whitespace); |
| if interpreter.is_empty() { |
| return error!(ENOEXEC); |
| } |
| |
| // The second part is the optional argument. Trim the leading and trailing whitespace, but |
| // treat the middle as a single argument, even if whitespace is encountered. |
| let begin = rest.iter().position(|b| !is_tab_or_space(b)).unwrap_or(rest.len()); |
| let end = rest.iter().rposition(|b| !is_tab_or_space(b)).map(|b| b + 1).unwrap_or(rest.len()); |
| let optional_arg = &rest[begin..end]; |
| |
| // SAFETY: `CString::new` can only fail if it encounters a null-byte, which we've made sure |
| // the input won't have. |
| Ok(if optional_arg.is_empty() { |
| vec![CString::new(interpreter).unwrap()] |
| } else { |
| vec![CString::new(interpreter).unwrap(), CString::new(optional_arg).unwrap()] |
| }) |
| } |
| |
| /// Resolves a file handle into a validated executable ELF. |
| fn resolve_elf( |
| current_task: &CurrentTask, |
| file: FileHandle, |
| vmo: zx::Vmo, |
| argv: Vec<CString>, |
| environ: Vec<CString>, |
| ) -> Result<ResolvedElf, Errno> { |
| let elf_headers = elf_parse::Elf64Headers::from_vmo(&vmo).map_err(elf_parse_error_to_errno)?; |
| let interp = if let Some(interp_hdr) = elf_headers |
| .program_header_with_type(elf_parse::SegmentType::Interp) |
| .map_err(|_| errno!(EINVAL))? |
| { |
| // The ELF header specified an ELF interpreter. |
| // Read the path and load this ELF as well. |
| let mut interp = vec![0; interp_hdr.filesz as usize]; |
| vmo.read(&mut interp, interp_hdr.offset as u64) |
| .map_err(|status| from_status_like_fdio!(status))?; |
| let interp = CStr::from_bytes_with_nul(&interp).map_err(|_| errno!(EINVAL))?; |
| let interp_file = current_task.open_file(interp.to_bytes(), OpenFlags::RDONLY)?; |
| let interp_vmo = interp_file.get_vmo( |
| current_task, |
| None, |
| zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_EXECUTE, |
| )?; |
| Some(ResolvedInterpElf { file: interp_file, vmo: interp_vmo }) |
| } else { |
| None |
| }; |
| Ok(ResolvedElf { file, vmo, interp, argv, environ }) |
| } |
| |
| /// Loads a resolved ELF into memory, along with an interpreter if one is defined, and initializes |
| /// the stack. |
| pub fn load_executable( |
| current_task: &CurrentTask, |
| resolved_elf: ResolvedElf, |
| original_path: &CStr, |
| ) -> Result<ThreadStartInfo, Errno> { |
| let main_elf = load_elf(resolved_elf.file, resolved_elf.vmo, ¤t_task.mm)?; |
| let interp_elf = resolved_elf |
| .interp |
| .map(|interp| load_elf(interp.file, interp.vmo, ¤t_task.mm)) |
| .transpose()?; |
| |
| let entry_elf = interp_elf.as_ref().unwrap_or(&main_elf); |
| let entry = UserAddress::from_ptr( |
| entry_elf.headers.file_header().entry.wrapping_add(entry_elf.vaddr_bias), |
| ); |
| |
| let dt_debug_address = parse_debug_addr(&main_elf); |
| |
| // TODO(tbodt): implement MAP_GROWSDOWN and then reset this to 1 page. The current value of |
| // this is based on adding 0x1000 each time a segfault appears. |
| let stack_size: usize = 0xf0000; |
| let stack_vmo = Arc::new(zx::Vmo::create(stack_size as u64).map_err(|_| errno!(ENOMEM))?); |
| stack_vmo |
| .as_ref() |
| .set_name(CStr::from_bytes_with_nul(b"[stack]\0").unwrap()) |
| .map_err(impossible_error)?; |
| let stack_base = current_task.mm.map( |
| DesiredAddress::Hint(UserAddress::default()), |
| Arc::clone(&stack_vmo), |
| 0, |
| stack_size, |
| zx::VmarFlags::PERM_READ | zx::VmarFlags::PERM_WRITE, |
| MappingOptions::empty(), |
| None, |
| )?; |
| let stack = stack_base + (stack_size - 8); |
| |
| // Fuchsia does not have a quantity that corresposnds to AT_CLKTCK, |
| // but we provide a typical value expected in *nix here. |
| const STARNIX_CLOCK_TICKS_PER_SEC: u64 = 100; |
| |
| let auxv = { |
| let state = current_task.read(); |
| let creds = &state.creds; |
| |
| vec![ |
| (AT_UID, creds.uid as u64), |
| (AT_EUID, creds.euid as u64), |
| (AT_GID, creds.gid as u64), |
| (AT_EGID, creds.egid as u64), |
| (AT_BASE, interp_elf.map_or(0, |interp| interp.file_base as u64)), |
| (AT_PAGESZ, *PAGE_SIZE), |
| (AT_PHDR, main_elf.file_base.wrapping_add(main_elf.headers.file_header().phoff) as u64), |
| (AT_PHNUM, main_elf.headers.file_header().phnum as u64), |
| ( |
| AT_ENTRY, |
| main_elf.vaddr_bias.wrapping_add(main_elf.headers.file_header().entry) as u64, |
| ), |
| (AT_CLKTCK, STARNIX_CLOCK_TICKS_PER_SEC), |
| (AT_SECURE, 0), |
| ] |
| }; |
| let stack = populate_initial_stack( |
| &stack_vmo, |
| original_path, |
| &resolved_elf.argv, |
| &resolved_elf.environ, |
| auxv, |
| stack_base, |
| stack, |
| )?; |
| |
| let mut mm_state = current_task.mm.state.write(); |
| mm_state.stack_base = stack_base; |
| mm_state.stack_size = stack_size; |
| mm_state.stack_start = stack; |
| |
| Ok(ThreadStartInfo { entry, stack, dt_debug_address }) |
| } |
| |
| /// Parses the debug address (`DT_DEBUG`) from the provided ELF. |
| /// |
| /// The debug address is read from the `elf_parse::SegmentType::Dynamic` program header, |
| /// if such a tag exists. |
| /// |
| /// Returns `None` if no debug tag exists. |
| fn parse_debug_addr(elf: &LoadedElf) -> Option<UserAddress> { |
| match elf.headers.program_header_with_type(elf_parse::SegmentType::Dynamic) { |
| Ok(Some(dynamic_header)) => { |
| const ENTRY_SIZE: usize = std::mem::size_of::<elf_parse::Elf64Dyn>(); |
| let mut header_bytes = vec![0u8; dynamic_header.filesz as usize]; |
| elf.vmo.read(&mut header_bytes, dynamic_header.offset as u64).ok()?; |
| |
| for offset in (0..(dynamic_header.filesz as usize)).step_by(ENTRY_SIZE) { |
| let mut dyn_entry = elf_parse::Elf64Dyn::default(); |
| let entry_range = offset..(offset + ENTRY_SIZE); |
| dyn_entry.as_bytes_mut().clone_from_slice(&header_bytes[entry_range]); |
| |
| if dyn_entry.tag() == Ok(elf_parse::Elf64DynTag::Debug) { |
| return Some(UserAddress::from( |
| elf.vaddr_bias.wrapping_add(dynamic_header.vaddr + offset) as u64, |
| )); |
| } |
| } |
| |
| None |
| } |
| _ => None, |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use crate::testing::*; |
| use assert_matches::assert_matches; |
| |
| #[::fuchsia::test] |
| fn test_trivial_initial_stack() { |
| let stack_vmo = zx::Vmo::create(0x4000).expect("VMO creation should succeed."); |
| let stack_base = UserAddress::from_ptr(0x3000_0000); |
| let original_stack_start_addr = UserAddress::from_ptr(0x3000_1000); |
| |
| let path = CString::new(&b""[..]).unwrap(); |
| let argv = &vec![]; |
| let environ = &vec![]; |
| |
| let stack_start_addr = populate_initial_stack( |
| &stack_vmo, |
| &path, |
| &argv, |
| &environ, |
| vec![], |
| stack_base, |
| original_stack_start_addr, |
| ) |
| .expect("Populate initial stack should succeed."); |
| |
| let argc_size: usize = 8; |
| let argv_terminator_size: usize = 8; |
| let environ_terminator_size: usize = 8; |
| let aux_execfn_terminator_size: usize = 8; |
| let aux_execfn: usize = 16; |
| let aux_random: usize = 16; |
| let aux_null: usize = 16; |
| let random_seed: usize = 16; |
| |
| let mut payload_size = argc_size |
| + argv_terminator_size |
| + environ_terminator_size |
| + aux_execfn_terminator_size |
| + aux_execfn |
| + aux_random |
| + aux_null |
| + random_seed; |
| payload_size += payload_size % 16; |
| |
| assert_eq!(stack_start_addr, original_stack_start_addr - payload_size); |
| } |
| |
| fn exec_hello_starnix(current_task: &mut CurrentTask) -> Result<(), Errno> { |
| let argv = vec![CString::new("bin/hello_starnix").unwrap()]; |
| current_task.exec(argv[0].clone(), argv, vec![])?; |
| Ok(()) |
| } |
| |
| #[::fuchsia::test] |
| fn test_load_hello_starnix() { |
| let (_kernel, mut current_task) = create_kernel_and_task_with_pkgfs(); |
| exec_hello_starnix(&mut current_task).expect("failed to load executable"); |
| assert!(current_task.mm.get_mapping_count() > 0); |
| } |
| |
| #[::fuchsia::test] |
| fn test_snapshot_hello_starnix() { |
| let (kernel, mut current_task) = create_kernel_and_task_with_pkgfs(); |
| exec_hello_starnix(&mut current_task).expect("failed to load executable"); |
| |
| let current2 = create_task(&kernel, "another-task"); |
| current_task.mm.snapshot_to(¤t2.mm).expect("failed to snapshot mm"); |
| |
| assert_eq!(current_task.mm.get_mapping_count(), current2.mm.get_mapping_count()); |
| } |
| |
| #[::fuchsia::test] |
| fn test_parse_interpreter_line() { |
| assert_matches!(parse_interpreter_line(b"#!"), Err(_)); |
| assert_matches!(parse_interpreter_line(b"#!\n"), Err(_)); |
| assert_matches!(parse_interpreter_line(b"#! \n"), Err(_)); |
| assert_matches!(parse_interpreter_line(b"#!/bin/bash"), Err(_)); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash\x00\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap()]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap()]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash -e\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap(), CString::new("-e").unwrap()]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash -e \n"), |
| Ok(vec![CString::new("/bin/bash").unwrap(), CString::new("-e").unwrap()]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash \t -e\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap(), CString::new("-e").unwrap()]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash -e -x\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap(), CString::new("-e -x").unwrap(),]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash -e -x\t-l\n"), |
| Ok(vec![CString::new("/bin/bash").unwrap(), CString::new("-e -x\t-l").unwrap(),]) |
| ); |
| assert_eq!( |
| parse_interpreter_line(b"#!/bin/bash\nfoobar"), |
| Ok(vec![CString::new("/bin/bash").unwrap()]) |
| ); |
| } |
| } |