#![cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
use std::fmt;
use std::io::{Read, Seek, SeekFrom};
use std::mem;
use std::result;
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize};
use crate::loader::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result};
use crate::loader_gen::elf;
pub use crate::loader_gen::start_info;
unsafe impl ByteValued for elf::Elf64_Ehdr {}
unsafe impl ByteValued for elf::Elf64_Nhdr {}
unsafe impl ByteValued for elf::Elf64_Phdr {}
#[derive(Debug, PartialEq)]
pub enum Error {
Align,
BigEndianElfOnLittle,
InvalidElfMagicNumber,
InvalidProgramHeaderSize,
InvalidProgramHeaderOffset,
InvalidProgramHeaderAddress,
InvalidEntryAddress,
Overflow,
ReadElfHeader,
ReadKernelImage,
ReadProgramHeader,
SeekKernelStart,
SeekElfStart,
SeekProgramHeader,
SeekNoteHeader,
ReadNoteHeader,
InvalidPvhNote,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = match self {
Error::Align => "Invalid alignment",
Error::BigEndianElfOnLittle => {
"Trying to load big-endian binary on little-endian machine"
}
Error::InvalidElfMagicNumber => "Invalid Elf magic number",
Error::InvalidProgramHeaderSize => "Invalid program header size",
Error::InvalidProgramHeaderOffset => "Invalid program header offset",
Error::InvalidProgramHeaderAddress => "Invalid Program Header Address",
Error::InvalidEntryAddress => "Invalid entry address",
Error::Overflow => "Overflow occurred during an arithmetic operation",
Error::ReadElfHeader => "Unable to read elf header",
Error::ReadKernelImage => "Unable to read kernel image",
Error::ReadProgramHeader => "Unable to read program header",
Error::SeekKernelStart => "Unable to seek to kernel start",
Error::SeekElfStart => "Unable to seek to elf start",
Error::SeekProgramHeader => "Unable to seek to program header",
Error::SeekNoteHeader => "Unable to seek to note header",
Error::ReadNoteHeader => "Unable to read note header",
Error::InvalidPvhNote => "Invalid PVH note header",
};
write!(f, "Kernel Loader: {}", desc)
}
}
impl std::error::Error for Error {}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PvhBootCapability {
PvhEntryPresent(GuestAddress),
PvhEntryNotPresent,
PvhEntryIgnored,
}
impl Default for PvhBootCapability {
fn default() -> Self {
PvhBootCapability::PvhEntryIgnored
}
}
impl fmt::Display for PvhBootCapability {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::PvhBootCapability::*;
match self {
PvhEntryPresent(pvh_entry_addr) => write!(
f,
"PVH entry point present at guest address: {:#x}",
pvh_entry_addr.raw_value()
),
PvhEntryNotPresent => write!(f, "PVH entry point not present"),
PvhEntryIgnored => write!(f, "PVH entry point ignored"),
}
}
}
pub struct Elf;
impl Elf {
fn validate_header(ehdr: &elf::Elf64_Ehdr) -> std::result::Result<(), Error> {
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>() {
return Err(Error::InvalidProgramHeaderOffset);
}
Ok(())
}
}
impl KernelLoader for Elf {
fn load<F, M: GuestMemory>(
guest_mem: &M,
kernel_offset: Option<GuestAddress>,
kernel_image: &mut F,
highmem_start_address: Option<GuestAddress>,
) -> Result<KernelLoaderResult>
where
F: Read + Seek,
{
kernel_image
.seek(SeekFrom::Start(0))
.map_err(|_| Error::SeekElfStart)?;
let mut ehdr = elf::Elf64_Ehdr::default();
ehdr.as_bytes()
.read_from(0, kernel_image, mem::size_of::<elf::Elf64_Ehdr>())
.map_err(|_| Error::ReadElfHeader)?;
Self::validate_header(&ehdr)?;
if let Some(addr) = highmem_start_address {
if (ehdr.e_entry as u64) < addr.raw_value() {
return Err(Error::InvalidEntryAddress.into());
}
}
let mut loader_result: KernelLoaderResult = Default::default();
loader_result.kernel_load = match kernel_offset {
Some(k_offset) => GuestAddress(
k_offset
.raw_value()
.checked_add(ehdr.e_entry as u64)
.ok_or(Error::Overflow)?,
),
None => GuestAddress(ehdr.e_entry as u64),
};
kernel_image
.seek(SeekFrom::Start(ehdr.e_phoff))
.map_err(|_| Error::SeekProgramHeader)?;
let phdr_sz = mem::size_of::<elf::Elf64_Phdr>();
let mut phdrs: Vec<elf::Elf64_Phdr> = vec![];
for _ in 0usize..ehdr.e_phnum as usize {
let mut phdr = elf::Elf64_Phdr::default();
phdr.as_bytes()
.read_from(0, kernel_image, phdr_sz)
.map_err(|_| Error::ReadProgramHeader)?;
phdrs.push(phdr);
}
for phdr in phdrs {
if phdr.p_type != elf::PT_LOAD || phdr.p_filesz == 0 {
if phdr.p_type == elf::PT_NOTE {
if let Some(_offset) = kernel_offset {
loader_result.pvh_boot_cap = PvhBootCapability::PvhEntryIgnored;
} else {
loader_result.pvh_boot_cap = parse_elf_note(&phdr, kernel_image)?;
}
}
continue;
}
kernel_image
.seek(SeekFrom::Start(phdr.p_offset))
.map_err(|_| Error::SeekKernelStart)?;
let mem_offset = match kernel_offset {
Some(k_offset) => k_offset
.checked_add(phdr.p_paddr as u64)
.ok_or(Error::InvalidProgramHeaderAddress)?,
None => GuestAddress(phdr.p_paddr as u64),
};
guest_mem
.read_exact_from(mem_offset, kernel_image, phdr.p_filesz as usize)
.map_err(|_| Error::ReadKernelImage)?;
let kernel_end = mem_offset
.raw_value()
.checked_add(phdr.p_memsz as GuestUsize)
.ok_or(KernelLoaderError::MemoryOverflow)?;
loader_result.kernel_end = std::cmp::max(loader_result.kernel_end, kernel_end);
}
loader_result.setup_header = None;
Ok(loader_result)
}
}
const PVH_NOTE_STR_SZ: usize = 4;
fn parse_elf_note<F>(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result<PvhBootCapability>
where
F: Read + Seek,
{
const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18;
let n_align = phdr.p_align;
kernel_image
.seek(SeekFrom::Start(phdr.p_offset))
.map_err(|_| Error::SeekNoteHeader)?;
let mut nhdr: elf::Elf64_Nhdr = Default::default();
let mut read_size: usize = 0;
let nhdr_sz = mem::size_of::<elf::Elf64_Nhdr>();
while read_size < phdr.p_filesz as usize {
nhdr.as_bytes()
.read_from(0, kernel_image, nhdr_sz)
.map_err(|_| Error::ReadNoteHeader)?;
if nhdr.n_type == XEN_ELFNOTE_PHYS32_ENTRY && nhdr.n_namesz as usize == PVH_NOTE_STR_SZ {
let mut buf = [0u8; PVH_NOTE_STR_SZ];
kernel_image
.read_exact(&mut buf)
.map_err(|_| Error::ReadNoteHeader)?;
if buf == [b'X', b'e', b'n', b'\0'] {
break;
}
}
let namesz_aligned = align_up(u64::from(nhdr.n_namesz), n_align)?;
let descsz_aligned = align_up(u64::from(nhdr.n_descsz), n_align)?;
read_size = read_size
.checked_add(nhdr_sz)
.and_then(|read_size| read_size.checked_add(namesz_aligned))
.and_then(|read_size| read_size.checked_add(descsz_aligned))
.ok_or(Error::Overflow)?;
kernel_image
.seek(SeekFrom::Start(phdr.p_offset + read_size as u64))
.map_err(|_| Error::SeekNoteHeader)?;
}
if read_size >= phdr.p_filesz as usize {
return Ok(PvhBootCapability::PvhEntryNotPresent);
}
kernel_image
.seek(SeekFrom::Current(
align_up(u64::from(nhdr.n_namesz), n_align)? as i64 - PVH_NOTE_STR_SZ as i64,
))
.map_err(|_| Error::SeekNoteHeader)?;
if (nhdr.n_descsz as usize) < mem::size_of::<u32>() {
return Err(Error::InvalidPvhNote.into());
}
let mut pvh_addr_bytes = [0; mem::size_of::<u32>()];
kernel_image
.read_exact(&mut pvh_addr_bytes)
.map_err(|_| Error::ReadNoteHeader)?;
Ok(PvhBootCapability::PvhEntryPresent(GuestAddress(
u32::from_le_bytes(pvh_addr_bytes).into(),
)))
}
fn align_up(addr: u64, align: u64) -> result::Result<usize, Error> {
if !align.is_power_of_two() {
return Err(Error::Align);
}
let align_mask = align - 1;
if addr & align_mask == 0 {
Ok(addr as usize)
} else {
Ok(((addr | align_mask) + 1) as usize)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
use vm_memory::{Address, GuestAddress, GuestMemoryMmap};
const MEM_SIZE: u64 = 0x100_0000;
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap()
}
fn make_elf_bin() -> Vec<u8> {
let mut v = Vec::new();
v.extend_from_slice(include_bytes!("test_elf.bin"));
v
}
fn make_elfnote() -> Vec<u8> {
include_bytes!("test_elfnote.bin").to_vec()
}
fn make_dummy_elfnote() -> Vec<u8> {
include_bytes!("test_dummynote.bin").to_vec()
}
fn make_bad_elfnote() -> Vec<u8> {
include_bytes!("test_badnote.bin").to_vec()
}
fn make_bad_align() -> Vec<u8> {
include_bytes!("test_align.bin").to_vec()
}
#[test]
fn test_load_elf() {
let gm = create_guest_mem();
let image = make_elf_bin();
let kernel_addr = GuestAddress(0x200000);
let mut highmem_start_address = GuestAddress(0x0);
let mut loader_result = Elf::load(
&gm,
Some(kernel_addr),
&mut Cursor::new(&image),
Some(highmem_start_address),
)
.unwrap();
assert_eq!(loader_result.kernel_load.raw_value(), 0x300000);
loader_result = Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap();
assert_eq!(loader_result.kernel_load.raw_value(), 0x300000);
loader_result = Elf::load(
&gm,
None,
&mut Cursor::new(&image),
Some(highmem_start_address),
)
.unwrap();
assert_eq!(loader_result.kernel_load.raw_value(), 0x100000);
highmem_start_address = GuestAddress(0xa00000);
assert_eq!(
Some(KernelLoaderError::Elf(Error::InvalidEntryAddress)),
Elf::load(
&gm,
None,
&mut Cursor::new(&image),
Some(highmem_start_address)
)
.err()
);
}
#[test]
fn test_bad_magic_number() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf_bin();
bad_image[0x1] = 0x33;
assert_eq!(
Some(KernelLoaderError::Elf(Error::InvalidElfMagicNumber)),
Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err()
);
}
#[test]
fn test_bad_endian() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf_bin();
bad_image[0x5] = 2;
assert_eq!(
Some(KernelLoaderError::Elf(Error::BigEndianElfOnLittle)),
Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err()
);
}
#[test]
fn test_bad_phoff() {
let gm = create_guest_mem();
let kernel_addr = GuestAddress(0x0);
let mut bad_image = make_elf_bin();
bad_image[0x20] = 0x10;
assert_eq!(
Some(KernelLoaderError::Elf(Error::InvalidProgramHeaderOffset)),
Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None).err()
);
}
#[test]
fn test_load_pvh() {
let gm = create_guest_mem();
let pvhnote_image = make_elfnote();
let loader_result = Elf::load(&gm, None, &mut Cursor::new(&pvhnote_image), None).unwrap();
assert_eq!(
loader_result.pvh_boot_cap,
PvhBootCapability::PvhEntryPresent(GuestAddress(0x1e1fe1f))
);
let loader_result = Elf::load(
&gm,
Some(GuestAddress(0x0020_0000)),
&mut Cursor::new(&pvhnote_image),
None,
)
.unwrap();
assert_eq!(
loader_result.pvh_boot_cap,
PvhBootCapability::PvhEntryIgnored
);
}
#[test]
fn test_dummy_elfnote() {
let gm = create_guest_mem();
let dummynote_image = make_dummy_elfnote();
let loader_result = Elf::load(&gm, None, &mut Cursor::new(&dummynote_image), None).unwrap();
assert_eq!(
loader_result.pvh_boot_cap,
PvhBootCapability::PvhEntryNotPresent
);
}
#[test]
fn test_bad_elfnote() {
let gm = create_guest_mem();
let badnote_image = make_bad_elfnote();
assert_eq!(
Some(KernelLoaderError::Elf(Error::InvalidPvhNote)),
Elf::load(&gm, None, &mut Cursor::new(&badnote_image), None).err()
);
}
#[test]
fn test_bad_align() {
let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (0x10_000_000usize))]).unwrap();
let bad_align_image = make_bad_align();
assert_eq!(
Some(KernelLoaderError::Elf(Error::Align)),
Elf::load(&gm, None, &mut Cursor::new(&bad_align_image), None).err()
);
}
#[test]
fn test_overflow_loadaddr() {
let gm = create_guest_mem();
let image = make_elf_bin();
assert_eq!(
Some(KernelLoaderError::Elf(Error::Overflow)),
Elf::load(
&gm,
Some(GuestAddress(u64::MAX)),
&mut Cursor::new(&image),
None
)
.err()
);
}
}