Skip to content

Commit

Permalink
pvh: Enable PVH boot support
Browse files Browse the repository at this point in the history
Parse the ELF header looking for a PVH Note section and retrieve
the encoded PVH entry point address if there is one. The entry
point address is returned in KernelLoaderResult alongside the
typical ELF entry point used for direct boot.
A VMM implementing KernelLoader can now determine whether a
PVH entry point is available and choose to configure its guests
to boot using either PVH or Linux 64-bit protocol.

Signed-off-by: Alejandro Jimenez <[email protected]>
  • Loading branch information
aljimenezb committed Feb 21, 2020
1 parent 1625209 commit 102b204
Showing 1 changed file with 102 additions and 0 deletions.
102 changes: 102 additions & 0 deletions src/loader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// Copyright © 2020, Oracle and/or its affiliates.
//
// Copyright (c) 2019 Intel Corporation. All rights reserved.
// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
Expand Down Expand Up @@ -36,6 +38,11 @@ use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize};
#[allow(missing_docs)]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
pub mod bootparam;

#[allow(missing_docs)]
#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))]
pub mod start_info;

#[allow(dead_code)]
#[allow(non_camel_case_types)]
#[allow(non_snake_case)]
Expand Down Expand Up @@ -93,6 +100,10 @@ pub enum Error {
SeekBzImageHeader,
/// Unable to seek to bzImage compressed kernel.
SeekBzImageCompressedKernel,
/// Unable to seek to note header.
SeekNoteHeader,
/// Unable to read note header.
ReadNoteHeader,
}

/// A specialized `Result` type for the kernel loader.
Expand Down Expand Up @@ -125,6 +136,8 @@ impl error::Error for Error {
Error::SeekBzImageEnd => "Unable to seek bzImage end",
Error::SeekBzImageHeader => "Unable to seek bzImage header",
Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel",
Error::SeekNoteHeader => "Unable to seek to note header",
Error::ReadNoteHeader => "Unable to read note header",
}
}
}
Expand All @@ -150,6 +163,10 @@ pub struct KernelLoaderResult {
/// This field is only for bzImage following https://www.kernel.org/doc/Documentation/x86/boot.txt
/// VMM should make use of it to fill zero page for bzImage direct boot.
pub setup_header: Option<bootparam::setup_header>,
/// This field optionally holds the address of a PVH entry point, indicating that
/// the kernel supports the PVH boot protocol as described in:
/// https://xenbits.xen.org/docs/unstable/misc/pvh.html
pub pvh_entry_addr: Option<GuestAddress>,
}

/// A kernel image loading support must implement the KernelLoader trait.
Expand Down Expand Up @@ -247,6 +264,10 @@ impl KernelLoader for Elf {
// 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 {
if phdr.p_type == elf::PT_NOTE {
// This segment describes a Note, check if PVH entry point is encoded.
loader_result.pvh_entry_addr = parse_elf_note(phdr, kernel_image)?;
}
continue;
}

Expand Down Expand Up @@ -280,6 +301,73 @@ impl KernelLoader for Elf {
}
}

#[cfg(feature = "elf")]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn parse_elf_note<F>(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result<Option<GuestAddress>>
where
F: Read + Seek,
{
let n_align = phdr.p_align;

// Seek to the beginning of the note segment
kernel_image
.seek(SeekFrom::Start(phdr.p_offset))
.map_err(|_| Error::SeekNoteHeader)?;

// Now that the segment has been found, we must locate an ELF note with the
// correct type that encodes the PVH entry point if there is one.
let mut nhdr: elf::Elf64_Nhdr = Default::default();
let mut read_size: usize = 0;

while read_size < phdr.p_filesz as usize {
unsafe {
// read_struct is safe when reading a POD struct.
// It can be used and dropped without issue.
struct_util::read_struct(kernel_image, &mut nhdr).map_err(|_| Error::ReadNoteHeader)?;
}
// If the note header found is not the desired one, keep reading until
// the end of the segment
if nhdr.n_type == elf::XEN_ELFNOTE_PHYS32_ENTRY {
break;
}
// Skip the note header plus the size of its fields (with alignment)
read_size += mem::size_of::<elf::Elf64_Nhdr>()
+ align_up(nhdr.n_namesz as u64, n_align)
+ align_up(nhdr.n_descsz as u64, n_align);

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(None); // PVH ELF note not found, nothing else to do.
}
// Otherwise the correct note type was found.
// The note header struct has already been read, so we can seek from the
// current position and just skip the name field contents.
kernel_image
.seek(SeekFrom::Current(
align_up(nhdr.n_namesz as u64, n_align) as i64
))
.map_err(|_| Error::SeekNoteHeader)?;

// We support 64bit kernels only and the PVH entry point is a 32bit address
// encoded in a 64 bit field, so we'll grab all 8 bytes.
let mut pvh_addr_bytes = [0; 8usize];

if nhdr.n_descsz as usize != pvh_addr_bytes.len() {
return Err(Error::ReadNoteHeader);
}

kernel_image
.read_exact(&mut pvh_addr_bytes)
.map_err(|_| Error::ReadNoteHeader)
.ok();

Ok(Some(GuestAddress(<u64>::from_le_bytes(pvh_addr_bytes))))
}

#[cfg(feature = "bzimage")]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
/// Big zImage (bzImage) kernel image support.
Expand Down Expand Up @@ -409,6 +497,20 @@ pub fn load_cmdline<M: GuestMemory>(
Ok(())
}

/// Align address upwards. Taken from x86_64 crate.
///
/// Returns the smallest x with alignment `align` so that x >= addr. The alignment must be
/// a power of 2.
fn align_up(addr: u64, align: u64) -> usize {
assert!(align.is_power_of_two(), "`align` must be a power of two");
let align_mask = align - 1;
if addr & align_mask == 0 {
addr as usize // already aligned
} else {
((addr | align_mask) + 1) as usize
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down

0 comments on commit 102b204

Please sign in to comment.