blob: f54f31d026a7bdc65cb5a94b60258cbf678e9505 [file] [log] [blame]
// 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, &current_task.mm)?;
let interp_elf = resolved_elf
.interp
.map(|interp| load_elf(interp.file, interp.vmo, &current_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(&current2.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()])
);
}
}