blob: 769b91e063a42d50e3de92704ed6b0aacdaca4e0 [file] [log] [blame]
// Copyright 2017 The Chromium OS 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 std::ffi::CStr;
use std::fmt::{self, Display};
use std::io::{Read, Seek, SeekFrom};
use std::mem;
use base::AsRawDescriptor;
use data_model::DataInit;
use vm_memory::{GuestAddress, GuestMemory};
#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
#[allow(non_upper_case_globals)]
#[allow(clippy::all)]
mod elf;
// Elf64_Ehdr is plain old data with no implicit padding.
unsafe impl data_model::DataInit for elf::Elf64_Ehdr {}
// Elf64_Phdr is plain old data with no implicit padding.
unsafe impl data_model::DataInit for elf::Elf64_Phdr {}
#[derive(Debug, PartialEq)]
pub enum Error {
BigEndianElfOnLittle,
CommandLineCopy,
CommandLineOverflow,
InvalidElfMagicNumber,
InvalidProgramHeaderSize,
InvalidProgramHeaderOffset,
InvalidProgramHeaderAddress,
InvalidProgramHeaderMemSize,
ReadElfHeader,
ReadKernelImage,
ReadProgramHeader,
SeekKernelStart,
SeekElfStart,
SeekProgramHeader,
}
pub type Result<T> = std::result::Result<T, Error>;
impl std::error::Error for Error {}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
let description = match self {
BigEndianElfOnLittle => "trying to load big-endian binary on little-endian machine",
CommandLineCopy => "failed writing command line to guest memory",
CommandLineOverflow => "command line overflowed guest memory",
InvalidElfMagicNumber => "invalid Elf magic number",
InvalidProgramHeaderSize => "invalid program header size",
InvalidProgramHeaderOffset => "invalid program header offset",
InvalidProgramHeaderAddress => "invalid Program Header Address",
InvalidProgramHeaderMemSize => "invalid Program Header memory size",
ReadElfHeader => "unable to read elf header",
ReadKernelImage => "unable to read kernel image",
ReadProgramHeader => "unable to read program header",
SeekKernelStart => "unable to seek to kernel start",
SeekElfStart => "unable to seek to elf start",
SeekProgramHeader => "unable to seek to program header",
};
write!(f, "kernel loader: {}", description)
}
}
/// Loads a kernel from a vmlinux elf image to a slice
///
/// # Arguments
///
/// * `guest_mem` - The guest memory region the kernel is written to.
/// * `kernel_start` - The offset into `guest_mem` at which to load the kernel.
/// * `kernel_image` - Input vmlinux image.
pub fn load_kernel<F>(
guest_mem: &GuestMemory,
kernel_start: GuestAddress,
mut kernel_image: &mut F,
) -> Result<u64>
where
F: Read + Seek + AsRawDescriptor,
{
kernel_image
.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekElfStart)?;
let ehdr = elf::Elf64_Ehdr::from_reader(&mut kernel_image).map_err(|_| Error::ReadElfHeader)?;
// Sanity checks
if ehdr.e_ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
|| ehdr.e_ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
|| ehdr.e_ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
|| ehdr.e_ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
{
return Err(Error::InvalidElfMagicNumber);
}
if ehdr.e_ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
return Err(Error::BigEndianElfOnLittle);
}
if ehdr.e_phentsize as usize != mem::size_of::<elf::Elf64_Phdr>() {
return Err(Error::InvalidProgramHeaderSize);
}
if (ehdr.e_phoff as usize) < mem::size_of::<elf::Elf64_Ehdr>() {
// If the program header is backwards, bail.
return Err(Error::InvalidProgramHeaderOffset);
}
kernel_image
.seek(SeekFrom::Start(ehdr.e_phoff))
.map_err(|_| Error::SeekProgramHeader)?;
let phdrs = (0..ehdr.e_phnum)
.enumerate()
.map(|_| {
elf::Elf64_Phdr::from_reader(&mut kernel_image).map_err(|_| Error::ReadProgramHeader)
})
.collect::<Result<Vec<elf::Elf64_Phdr>>>()?;
let mut kernel_end = 0;
// Read in each section pointed to by the program headers.
for phdr in &phdrs {
if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 {
continue;
}
kernel_image
.seek(SeekFrom::Start(phdr.p_offset))
.map_err(|_| Error::SeekKernelStart)?;
let mem_offset = kernel_start
.checked_add(phdr.p_paddr)
.ok_or(Error::InvalidProgramHeaderAddress)?;
guest_mem
.read_to_memory(mem_offset, kernel_image, phdr.p_filesz as usize)
.map_err(|_| Error::ReadKernelImage)?;
kernel_end = mem_offset
.offset()
.checked_add(phdr.p_memsz)
.ok_or(Error::InvalidProgramHeaderMemSize)?;
}
Ok(kernel_end)
}
/// 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(())
}
#[cfg(test)]
mod test {
use super::*;
use std::fs::File;
use std::io::Write;
use tempfile::tempfile;
use vm_memory::{GuestAddress, GuestMemory};
const MEM_SIZE: u64 = 0x8000;
fn create_guest_mem() -> GuestMemory {
GuestMemory::new(&vec![(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, '1' as u8);
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, '2' as u8);
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, '3' as u8);
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, '4' as u8);
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
assert_eq!(val, '\0' as u8);
}
// Elf64 image that prints hello world on x86_64.
fn make_elf_bin() -> File {
let elf_bytes = include_bytes!("test_elf.bin");
let mut file = tempfile().expect("failed to create tempfile");
file.write_all(elf_bytes)
.expect("failed to write elf to shared memoy");
file
}
fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
f.seek(SeekFrom::Start(offset))
.expect("failed to seek file");
f.write(&[val])
.expect("failed to write mutated value to file");
}
#[test]
fn load_elf() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut image = make_elf_bin();
assert_eq!(Ok(16613), load_kernel(&gm, kernel_addr, &mut image));
}
#[test]
fn bad_magic() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf_bin();
mutate_elf_bin(&bad_image, 0x1, 0x33);
assert_eq!(
Err(Error::InvalidElfMagicNumber),
load_kernel(&gm, kernel_addr, &mut bad_image)
);
}
#[test]
fn bad_endian() {
// Only little endian is supported
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf_bin();
mutate_elf_bin(&bad_image, 0x5, 2);
assert_eq!(
Err(Error::BigEndianElfOnLittle),
load_kernel(&gm, kernel_addr, &mut bad_image)
);
}
#[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_elf_bin();
mutate_elf_bin(&bad_image, 0x20, 0x10);
assert_eq!(
Err(Error::InvalidProgramHeaderOffset),
load_kernel(&gm, kernel_addr, &mut bad_image)
);
}
}