blob: 2bbf3202fdc030eccea8317844802cb1a6b61419 [file] [log] [blame]
// Copyright 2019 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 {
crate::elf_parse as elf,
crate::util,
fuchsia_zircon::{self as zx, AsHandleRef},
std::ffi::{CStr, CString},
thiserror::Error,
};
/// Possible errors that can occur during ELF loading.
#[allow(missing_docs)] // No docs on individual error variants.
#[derive(Error, Debug)]
pub enum ElfLoadError {
#[error("ELF load segments were empty")]
NothingToLoad,
#[error("Failed to allocate VMAR for ELF: {}", _0)]
VmarAllocate(zx::Status),
#[error("Failed to map VMAR: {}", _0)]
VmarMap(zx::Status),
#[error("Failed to create CoW VMO clone: {}", _0)]
VmoCowClone(zx::Status),
#[error("Failed to create VMO: {}", _0)]
VmoCreate(zx::Status),
#[error("Failed to read from VMO: {}", _0)]
VmoRead(zx::Status),
#[error("Failed to write to VMO: {}", _0)]
VmoWrite(zx::Status),
#[error("Failed to get VMO name: {}", _0)]
GetVmoName(zx::Status),
#[error("Failed to set VMO name: {}", _0)]
SetVmoName(zx::Status),
}
impl ElfLoadError {
/// Returns an appropriate zx::Status code for the given error.
pub fn as_zx_status(&self) -> zx::Status {
match self {
ElfLoadError::NothingToLoad => zx::Status::NOT_FOUND,
ElfLoadError::VmarAllocate(s)
| ElfLoadError::VmarMap(s)
| ElfLoadError::VmoCowClone(s)
| ElfLoadError::VmoCreate(s)
| ElfLoadError::VmoRead(s)
| ElfLoadError::VmoWrite(s)
| ElfLoadError::GetVmoName(s)
| ElfLoadError::SetVmoName(s) => *s,
}
}
}
#[derive(Debug)]
pub struct LoadedElf {
/// The VMAR that the ELF file was loaded into.
pub vmar: zx::Vmar,
/// The virtual address of the VMAR.
pub vmar_base: usize,
/// The ELF entry point, adjusted for the base address of the VMAR.
pub entry: usize,
}
pub fn load_elf(
vmo: &zx::Vmo,
root_vmar: &zx::Vmar,
headers: &elf::Elf64Headers,
) -> Result<LoadedElf, ElfLoadError> {
let elf_vmar = ElfVmar::allocate(root_vmar, headers)?;
elf_vmar.map_segments(vmo, headers)?;
Ok(LoadedElf {
vmar: elf_vmar.vmar,
vmar_base: elf_vmar.vmar_base,
entry: headers.file_header().entry.wrapping_add(elf_vmar.vaddr_bias),
})
}
struct ElfVmar {
vmar: zx::Vmar,
vmar_base: usize,
/// Difference between p_vaddr addresses in the ELF headers and the actual mapped virtual
/// address within the root VMAR.
vaddr_bias: usize,
}
impl ElfVmar {
/// Allocates a new VMAR within the given root VMAR large enough and with appropriate mapping
/// permissions for the given ELF file. The kernel chooses where the VMAR is located for ASLR.
fn allocate(root_vmar: &zx::Vmar, headers: &elf::Elf64Headers) -> Result<Self, ElfLoadError> {
let (mut first, mut low, mut high) = (false, 0, 0);
let mut max_perm = elf::SegmentFlags::empty();
for hdr in headers.program_headers_with_type(elf::SegmentType::Load) {
// elf_parse already checked that segments are ordered by vaddr and do not overlap.
if first {
low = util::page_start(hdr.vaddr);
first = true;
}
high = util::page_end(hdr.vaddr + hdr.memsz as usize);
max_perm |= hdr.flags();
}
let size = high - low;
if size == 0 {
return Err(ElfLoadError::NothingToLoad);
}
// Individual mappings with be restricted based on segment permissions, but we also limit
// the overall VMAR to the maximum permissions required across all load segments.
let flags = zx::VmarFlags::CAN_MAP_SPECIFIC | elf_to_vmar_can_map_flags(&max_perm);
let (vmar, vmar_base) =
root_vmar.allocate(0, size, flags).map_err(|s| ElfLoadError::VmarAllocate(s))?;
// We intentionally use wrapping subtraction here, in case the ELF file happens to use
// vaddr's that are higher than the VMAR base chosen by the kernel. Wrapping addition will
// be used when adding this bias to vaddr values.
Ok(ElfVmar { vmar, vmar_base, vaddr_bias: vmar_base.wrapping_sub(low) })
}
fn map_segments(&self, vmo: &zx::Vmo, headers: &elf::Elf64Headers) -> Result<(), ElfLoadError> {
// Get the relative bias between p_vaddr addresses in the headers and the allocated VMAR,
// rather than for the root VMAR. Should be equal to the first segment's starting vaddr
// negated, so that the first mapping starts at 0 within the allocated VMAR.
let rel_bias = self.vaddr_bias.wrapping_sub(self.vmar_base);
let vmo_name = vmo.get_name().map_err(|s| ElfLoadError::GetVmoName(s))?;
let mut first = true;
for hdr in headers.program_headers_with_type(elf::SegmentType::Load) {
// Sanity check relative bias calculation.
if first {
assert!(rel_bias == hdr.vaddr.wrapping_neg());
first = false;
}
// Map in all whole pages that this segment touches. Calculate the virtual address
// range that this mapping needs to cover. These addresses are relative to the
// allocated VMAR, not the root VMAR.
let vaddr_start = hdr.vaddr.wrapping_add(rel_bias);
let map_start = util::page_start(vaddr_start);
let map_end = util::page_end(vaddr_start + hdr.memsz as usize);
let map_size = map_end - map_start;
if map_size == 0 {
// Empty segment, ignore and map others.
continue;
}
// Calculate the pages from the VMO that need to be mapped.
let offset_end = hdr.offset + hdr.filesz as usize;
let mut vmo_start = util::page_start(hdr.offset);
let mut vmo_full_page_end = util::page_start(offset_end);
let vmo_partial_page_size = util::page_offset(offset_end);
// Page aligned size of VMO content to be mapped in, including any partial pages.
let vmo_size = util::page_end(offset_end) - vmo_start;
assert!(map_size >= vmo_size);
// If this segment is writeable (and we're mapping in some VMO content, i.e. it's not
// all zero initialized), create a writeable clone of the VMO.
let vmo_to_map: &zx::Vmo;
let writeable_vmo: zx::Vmo;
if vmo_size == 0 || !hdr.flags().contains(elf::SegmentFlags::WRITE) {
vmo_to_map = vmo;
} else {
writeable_vmo = vmo
.create_child(
zx::VmoChildOptions::COPY_ON_WRITE,
vmo_start as u64,
vmo_size as u64,
)
.map_err(ElfLoadError::VmoCowClone)?;
writeable_vmo
.set_name(&vmo_name_with_prefix(&vmo_name, VMO_NAME_PREFIX_DATA))
.map_err(ElfLoadError::SetVmoName)?;
vmo_to_map = &writeable_vmo;
// Update addresses into the VMO that will be mapped.
vmo_full_page_end -= vmo_start;
vmo_start = 0;
}
// If the mapping size is equal in size to the data to be mapped, then nothing else to
// do. Create the mapping and we're done with this segment.
let flags = zx::VmarFlags::SPECIFIC | elf_to_vmar_perm_flags(&hdr.flags());
if map_size == vmo_size {
self.vmar
.map(map_start, vmo_to_map, vmo_start as u64, vmo_size, flags)
.map_err(ElfLoadError::VmarMap)?;
continue;
}
// Mapping size is larger than the vmo data size (i.e. the segment contains a .bss
// section). The mapped region beyond the vmo size is zero initialized. We can start
// out by mapping any full pages from the vmo.
let vmo_full_page_size = vmo_full_page_end - vmo_start;
if vmo_full_page_size > 0 {
self.vmar
.map(map_start, vmo_to_map, vmo_start as u64, vmo_full_page_size, flags)
.map_err(ElfLoadError::VmarMap)?;
}
// Remaining pages are backed by an anonymous VMO, which is automatically zero filled
// by the kernel as needed.
let anon_map_start = map_start + vmo_full_page_size;
let anon_size = map_size - vmo_full_page_size;
let anon_vmo =
zx::Vmo::create(anon_size as u64).map_err(|s| ElfLoadError::VmoCreate(s))?;
anon_vmo
.set_name(&vmo_name_with_prefix(&vmo_name, VMO_NAME_PREFIX_BSS))
.map_err(ElfLoadError::SetVmoName)?;
// If the segment has a partial page of data at the end, it needs to be copied into the
// anonymous VMO.
if vmo_partial_page_size > 0 {
let mut page_buf = [0u8; util::PAGE_SIZE];
let buf = &mut page_buf[0..vmo_partial_page_size];
vmo_to_map.read(buf, vmo_full_page_end as u64).map_err(ElfLoadError::VmoRead)?;
anon_vmo.write(buf, 0).map_err(|s| ElfLoadError::VmoWrite(s))?;
}
// Map the anonymous vmo and done with this segment!
self.vmar
.map(anon_map_start, &anon_vmo, 0, anon_size, flags)
.map_err(ElfLoadError::VmarMap)?;
}
Ok(())
}
}
// These must not be longer than zx::sys::ZX_MAX_NAME_LEN.
const VMO_NAME_UNKNOWN: &[u8] = b"<unknown ELF>";
const VMO_NAME_PREFIX_BSS: &[u8] = b"bss:";
const VMO_NAME_PREFIX_DATA: &[u8] = b"data:";
// prefix length must be less than zx::sys::ZX_MAX_NAME_LEN-1 and not contain any nul bytes.
fn vmo_name_with_prefix(name: &CStr, prefix: &[u8]) -> CString {
const MAX_LEN: usize = zx::sys::ZX_MAX_NAME_LEN - 1;
assert!(prefix.len() <= MAX_LEN);
let mut name_bytes = name.to_bytes();
if name_bytes.len() == 0 {
name_bytes = VMO_NAME_UNKNOWN;
}
let name_len = std::cmp::min(MAX_LEN, prefix.len() + name_bytes.len());
let suffix_len = name_len - prefix.len();
let mut buf = Vec::with_capacity(name_len);
buf.extend_from_slice(prefix);
buf.extend_from_slice(&name_bytes[..suffix_len]);
assert!(buf.len() <= MAX_LEN);
// The input name is already a CStr, so it doesn't contain nul, so this should only fail if the
// prefix contains a nul, and since the prefixes are constants, panic if this fails.
CString::new(buf).expect("Unexpected nul byte in prefix")
}
fn elf_to_vmar_can_map_flags(elf_flags: &elf::SegmentFlags) -> zx::VmarFlags {
let mut flags = zx::VmarFlags::empty();
if elf_flags.contains(elf::SegmentFlags::READ) {
flags |= zx::VmarFlags::CAN_MAP_READ;
}
if elf_flags.contains(elf::SegmentFlags::WRITE) {
flags |= zx::VmarFlags::CAN_MAP_WRITE;
}
if elf_flags.contains(elf::SegmentFlags::EXECUTE) {
flags |= zx::VmarFlags::CAN_MAP_EXECUTE;
}
flags
}
fn elf_to_vmar_perm_flags(elf_flags: &elf::SegmentFlags) -> zx::VmarFlags {
let mut flags = zx::VmarFlags::empty();
if elf_flags.contains(elf::SegmentFlags::READ) {
flags |= zx::VmarFlags::PERM_READ;
}
if elf_flags.contains(elf::SegmentFlags::WRITE) {
flags |= zx::VmarFlags::PERM_WRITE;
}
if elf_flags.contains(elf::SegmentFlags::EXECUTE) {
flags |= zx::VmarFlags::PERM_EXECUTE;
}
flags
}
#[cfg(test)]
mod tests {
use {super::*, anyhow::Error};
#[test]
fn test_vmo_name_with_prefix() -> Result<(), Error> {
let empty_vmo_name = CStr::from_bytes_with_nul(b"\0")?;
let short_vmo_name = CStr::from_bytes_with_nul(b"short_vmo_name\0")?;
let max_vmo_name = CStr::from_bytes_with_nul(b"a_great_maximum_length_vmo_name\0")?;
assert_eq!(
vmo_name_with_prefix(&empty_vmo_name, VMO_NAME_PREFIX_BSS).as_bytes(),
b"bss:<unknown ELF>"
);
assert_eq!(
vmo_name_with_prefix(&short_vmo_name, VMO_NAME_PREFIX_BSS).as_bytes(),
b"bss:short_vmo_name"
);
assert_eq!(
vmo_name_with_prefix(&max_vmo_name, VMO_NAME_PREFIX_BSS).as_bytes(),
b"bss:a_great_maximum_length_vmo_"
);
assert_eq!(
vmo_name_with_prefix(&max_vmo_name, VMO_NAME_PREFIX_DATA).as_bytes(),
b"data:a_great_maximum_length_vmo"
);
assert_eq!(
vmo_name_with_prefix(&empty_vmo_name, b"a_long_vmo_name_prefix:").as_bytes(),
b"a_long_vmo_name_prefix:<unknown"
);
assert_eq!(
vmo_name_with_prefix(&empty_vmo_name, max_vmo_name.to_bytes()).as_bytes(),
max_vmo_name.to_bytes()
);
assert_eq!(
vmo_name_with_prefix(&max_vmo_name, max_vmo_name.to_bytes()).as_bytes(),
max_vmo_name.to_bytes()
);
Ok(())
}
}