blob: add5d9ce4a14a4c05f0d707b692fc5271ba7a723 [file] [edit]
// Copyright 2017 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//! Linux kernel ELF file loader.
use std::ffi::CStr;
use std::io::Read;
use std::io::Seek;
use std::io::SeekFrom;
use std::mem;
use base::AsRawDescriptor;
use data_model::zerocopy_from_reader;
use remain::sorted;
use resources::AddressRange;
use thiserror::Error;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
use zerocopy::FromBytes;
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
#[allow(clippy::all)]
mod elf;
mod arm64;
pub use arm64::load_arm64_kernel;
#[sorted]
#[derive(Error, Debug, PartialEq, Eq)]
pub enum Error {
#[error("trying to load big-endian binary on little-endian machine")]
BigEndianOnLittle,
#[error("failed writing command line to guest memory")]
CommandLineCopy,
#[error("command line overflowed guest memory")]
CommandLineOverflow,
#[error("invalid elf class")]
InvalidElfClass,
#[error("invalid elf version")]
InvalidElfVersion,
#[error("invalid entry point")]
InvalidEntryPoint,
#[error("invalid kernel offset")]
InvalidKernelOffset,
#[error("invalid kernel size")]
InvalidKernelSize,
#[error("invalid magic number")]
InvalidMagicNumber,
#[error("invalid Program Header Address")]
InvalidProgramHeaderAddress,
#[error("invalid Program Header memory size")]
InvalidProgramHeaderMemSize,
#[error("invalid program header offset")]
InvalidProgramHeaderOffset,
#[error("invalid program header size")]
InvalidProgramHeaderSize,
#[error("no loadable program headers found")]
NoLoadableProgramHeaders,
#[error("program header address out of allowed address range")]
ProgramHeaderAddressOutOfRange,
#[error("unable to read header")]
ReadHeader,
#[error("unable to read kernel image")]
ReadKernelImage,
#[error("unable to read program header")]
ReadProgramHeader,
#[error("unable to seek to kernel end")]
SeekKernelEnd,
#[error("unable to seek to kernel start")]
SeekKernelStart,
#[error("unable to seek to program header")]
SeekProgramHeader,
}
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
/// Information about a kernel loaded with the [`load_elf`] function.
pub struct LoadedKernel {
/// Address range containg the bounds of the loaded program headers.
/// `address_range.start` is the start of the lowest loaded program header.
/// `address_range.end` is the end of the highest loaded program header.
pub address_range: AddressRange,
/// Size of the kernel image in bytes.
pub size: u64,
/// Entry point address of the kernel.
pub entry: GuestAddress,
}
/// Loads a kernel from a 32-bit ELF image into memory.
///
/// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
/// program headers.
///
/// # Arguments
///
/// * `guest_mem` - The guest memory region the kernel is written to.
/// * `kernel_start` - The minimum guest address to allow when loading program headers.
/// * `kernel_image` - Input vmlinux image.
/// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
pub fn load_elf32<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
kernel_image: &mut F,
phys_offset: u64,
) -> Result<LoadedKernel>
where
F: Read + Seek + AsRawDescriptor,
{
load_elf_for_class(
guest_mem,
kernel_start,
kernel_image,
phys_offset,
Some(elf::ELFCLASS32),
)
}
/// Loads a kernel from a 64-bit ELF image into memory.
///
/// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
/// program headers.
///
/// # Arguments
///
/// * `guest_mem` - The guest memory region the kernel is written to.
/// * `kernel_start` - The minimum guest address to allow when loading program headers.
/// * `kernel_image` - Input vmlinux image.
/// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
pub fn load_elf64<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
kernel_image: &mut F,
phys_offset: u64,
) -> Result<LoadedKernel>
where
F: Read + Seek + AsRawDescriptor,
{
load_elf_for_class(
guest_mem,
kernel_start,
kernel_image,
phys_offset,
Some(elf::ELFCLASS64),
)
}
/// Loads a kernel from a 32-bit or 64-bit ELF image into memory.
///
/// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
/// program headers.
///
/// # Arguments
///
/// * `guest_mem` - The guest memory region the kernel is written to.
/// * `kernel_start` - The minimum guest address to allow when loading program headers.
/// * `kernel_image` - Input vmlinux image.
/// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
pub fn load_elf<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
kernel_image: &mut F,
phys_offset: u64,
) -> Result<LoadedKernel>
where
F: Read + Seek + AsRawDescriptor,
{
load_elf_for_class(guest_mem, kernel_start, kernel_image, phys_offset, None)
}
fn load_elf_for_class<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
kernel_image: &mut F,
phys_offset: u64,
ei_class: Option<u32>,
) -> Result<LoadedKernel>
where
F: Read + Seek + AsRawDescriptor,
{
let elf = read_elf(kernel_image, ei_class)?;
let mut start = None;
let mut end = 0;
// Read in each section pointed to by the program headers.
for phdr in &elf.program_headers {
if phdr.p_type != elf::PT_LOAD {
continue;
}
let paddr = phdr
.p_paddr
.checked_add(phys_offset)
.ok_or(Error::ProgramHeaderAddressOutOfRange)?;
if paddr < kernel_start.offset() {
return Err(Error::ProgramHeaderAddressOutOfRange);
}
if start.is_none() {
start = Some(paddr);
}
end = paddr
.checked_add(phdr.p_memsz)
.ok_or(Error::InvalidProgramHeaderMemSize)?;
if phdr.p_filesz == 0 {
continue;
}
kernel_image
.seek(SeekFrom::Start(phdr.p_offset))
.map_err(|_| Error::SeekKernelStart)?;
guest_mem
.read_to_memory(GuestAddress(paddr), kernel_image, phdr.p_filesz as usize)
.map_err(|_| Error::ReadKernelImage)?;
}
// We should have found at least one PT_LOAD program header. If not, `start` will not be set.
let start = start.ok_or(Error::NoLoadableProgramHeaders)?;
let size = end
.checked_sub(start)
.ok_or(Error::InvalidProgramHeaderSize)?;
let address_range = AddressRange { start, end };
// The entry point address must fall within one of the loaded sections.
// We approximate this by checking whether it within the bounds of the first and last sections.
let entry = elf
.file_header
.e_entry
.checked_add(phys_offset)
.ok_or(Error::InvalidEntryPoint)?;
if !address_range.contains(entry) {
return Err(Error::InvalidEntryPoint);
}
Ok(LoadedKernel {
address_range,
size,
entry: GuestAddress(entry),
})
}
/// Writes the command line string to the given memory slice.
///
/// # Arguments
///
/// * `guest_mem` - A u8 slice that will be partially overwritten by the command line.
/// * `guest_addr` - The address in `guest_mem` at which to load the command line.
/// * `cmdline` - The kernel command line.
pub fn load_cmdline(
guest_mem: &GuestMemory,
guest_addr: GuestAddress,
cmdline: &CStr,
) -> Result<()> {
let len = cmdline.to_bytes().len();
if len == 0 {
return Ok(());
}
let end = guest_addr
.checked_add(len as u64 + 1)
.ok_or(Error::CommandLineOverflow)?; // Extra for null termination.
if end > guest_mem.end_addr() {
return Err(Error::CommandLineOverflow);
}
guest_mem
.write_at_addr(cmdline.to_bytes_with_nul(), guest_addr)
.map_err(|_| Error::CommandLineCopy)?;
Ok(())
}
struct Elf64 {
file_header: elf::Elf64_Ehdr,
program_headers: Vec<elf::Elf64_Phdr>,
}
/// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
/// converted to ELF64 format. If `required_ei_class` is Some and the file's ELF ei_class doesn't
/// match, an Err is returned.
fn read_elf<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64>
where
F: Read + Seek + AsRawDescriptor,
{
// Read the ELF identification (e_ident) block.
file.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekKernelStart)?;
let mut ident = [0u8; 16];
file.read_exact(&mut ident).map_err(|_| Error::ReadHeader)?;
// e_ident checks
if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
|| ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
|| ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
|| ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
{
return Err(Error::InvalidMagicNumber);
}
if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
return Err(Error::BigEndianOnLittle);
}
if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 {
return Err(Error::InvalidElfVersion);
}
let ei_class = ident[elf::EI_CLASS as usize] as u32;
if let Some(required_ei_class) = required_ei_class {
if ei_class != required_ei_class {
return Err(Error::InvalidElfClass);
}
}
match ei_class {
elf::ELFCLASS32 => read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file),
elf::ELFCLASS64 => read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file),
_ => Err(Error::InvalidElfClass),
}
}
/// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
/// converted to ELF64 format. `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr
/// types to read from the file. Caller should check that `file` is a valid ELF file before calling
/// this function.
fn read_elf_by_type<F, FileHeader, ProgramHeader>(mut file: &mut F) -> Result<Elf64>
where
F: Read + Seek + AsRawDescriptor,
FileHeader: FromBytes + Default + Into<elf::Elf64_Ehdr>,
ProgramHeader: FromBytes + Default + Into<elf::Elf64_Phdr>,
{
file.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekKernelStart)?;
let ehdr: FileHeader = zerocopy_from_reader(&mut file).map_err(|_| Error::ReadHeader)?;
let ehdr: elf::Elf64_Ehdr = ehdr.into();
if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() {
return Err(Error::InvalidProgramHeaderSize);
}
if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() {
// If the program header is backwards, bail.
return Err(Error::InvalidProgramHeaderOffset);
}
file.seek(SeekFrom::Start(ehdr.e_phoff as u64))
.map_err(|_| Error::SeekProgramHeader)?;
let phdrs: Vec<ProgramHeader> = (0..ehdr.e_phnum)
.enumerate()
.map(|_| zerocopy_from_reader(&mut file).map_err(|_| Error::ReadProgramHeader))
.collect::<Result<Vec<ProgramHeader>>>()?;
Ok(Elf64 {
file_header: ehdr,
program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(),
})
}
impl From<elf::Elf32_Ehdr> for elf::Elf64_Ehdr {
fn from(ehdr32: elf::Elf32_Ehdr) -> Self {
elf::Elf64_Ehdr {
e_ident: ehdr32.e_ident,
e_type: ehdr32.e_type as elf::Elf64_Half,
e_machine: ehdr32.e_machine as elf::Elf64_Half,
e_version: ehdr32.e_version as elf::Elf64_Word,
e_entry: ehdr32.e_entry as elf::Elf64_Addr,
e_phoff: ehdr32.e_phoff as elf::Elf64_Off,
e_shoff: ehdr32.e_shoff as elf::Elf64_Off,
e_flags: ehdr32.e_flags as elf::Elf64_Word,
e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half,
e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half,
e_phnum: ehdr32.e_phnum as elf::Elf64_Half,
e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half,
e_shnum: ehdr32.e_shnum as elf::Elf64_Half,
e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half,
}
}
}
impl From<elf::Elf32_Phdr> for elf::Elf64_Phdr {
fn from(phdr32: elf::Elf32_Phdr) -> Self {
elf::Elf64_Phdr {
p_type: phdr32.p_type as elf::Elf64_Word,
p_flags: phdr32.p_flags as elf::Elf64_Word,
p_offset: phdr32.p_offset as elf::Elf64_Off,
p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr,
p_paddr: phdr32.p_paddr as elf::Elf64_Addr,
p_filesz: phdr32.p_filesz as elf::Elf64_Xword,
p_memsz: phdr32.p_memsz as elf::Elf64_Xword,
p_align: phdr32.p_align as elf::Elf64_Xword,
}
}
}
#[cfg(test)]
mod test {
use std::fs::File;
use std::io::Write;
use tempfile::tempfile;
use vm_memory::GuestAddress;
use vm_memory::GuestMemory;
use super::*;
const MEM_SIZE: u64 = 0x40_0000;
fn create_guest_mem() -> GuestMemory {
GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
}
#[test]
fn cmdline_overflow() {
let gm = create_guest_mem();
let cmdline_address = GuestAddress(MEM_SIZE - 5);
assert_eq!(
Err(Error::CommandLineOverflow),
load_cmdline(
&gm,
cmdline_address,
CStr::from_bytes_with_nul(b"12345\0").unwrap()
)
);
}
#[test]
fn cmdline_write_end() {
let gm = create_guest_mem();
let mut cmdline_address = GuestAddress(45);
assert_eq!(
Ok(()),
load_cmdline(
&gm,
cmdline_address,
CStr::from_bytes_with_nul(b"1234\0").unwrap()
)
);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, b'1');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, b'2');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, b'3');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, b'4');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, b'\0');
}
// Elf32 image that prints hello world on x86.
fn make_elf32_bin() -> File {
// test_elf32.bin built on Linux with gcc -m32 -static-pie
let bytes = include_bytes!("test_elf32.bin");
make_elf_bin(bytes)
}
// Elf64 image that prints hello world on x86_64.
fn make_elf64_bin() -> File {
let bytes = include_bytes!("test_elf64.bin");
make_elf_bin(bytes)
}
fn make_elf_bin(elf_bytes: &[u8]) -> File {
let mut file = tempfile().expect("failed to create tempfile");
file.write_all(elf_bytes)
.expect("failed to write elf to shared memory");
file
}
fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
f.seek(SeekFrom::Start(offset))
.expect("failed to seek file");
f.write_all(&[val])
.expect("failed to write mutated value to file");
}
#[test]
fn load_elf32() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut image = make_elf32_bin();
let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap();
assert_eq!(kernel.address_range.start, 0);
assert_eq!(kernel.address_range.end, 0xa_2038);
assert_eq!(kernel.size, 0xa_2038);
assert_eq!(kernel.entry, GuestAddress(0x3dc0));
}
#[test]
fn load_elf64() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut image = make_elf64_bin();
let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF");
assert_eq!(kernel.address_range.start, 0x20_0000);
assert_eq!(kernel.address_range.end, 0x20_0035);
assert_eq!(kernel.size, 0x35);
assert_eq!(kernel.entry, GuestAddress(0x20_000e));
}
#[test]
fn bad_magic() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf64_bin();
mutate_elf_bin(&bad_image, 0x1, 0x33);
assert_eq!(
Err(Error::InvalidMagicNumber),
load_elf(&gm, kernel_addr, &mut bad_image, 0)
);
}
#[test]
fn bad_endian() {
// Only little endian is supported
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x20_0000);
let mut bad_image = make_elf64_bin();
mutate_elf_bin(&bad_image, 0x5, 2);
assert_eq!(
Err(Error::BigEndianOnLittle),
load_elf(&gm, kernel_addr, &mut bad_image, 0)
);
}
#[test]
fn bad_phoff() {
// program header has to be past the end of the elf header
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf64_bin();
mutate_elf_bin(&bad_image, 0x20, 0x10);
assert_eq!(
Err(Error::InvalidProgramHeaderOffset),
load_elf(&gm, kernel_addr, &mut bad_image, 0)
);
}
#[test]
fn paddr_below_start() {
let gm = create_guest_mem();
// test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address
let kernel_addr = GuestAddress(0x30_0000);
let mut image = make_elf64_bin();
let res = load_elf(&gm, kernel_addr, &mut image, 0);
assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange));
}
}