From c7e26644ca3f16254237164de4d70658c618e194 Mon Sep 17 00:00:00 2001 From: Alexandra Iordache Date: Tue, 10 Mar 2020 13:26:20 +0200 Subject: [PATCH 1/4] code reorganization: elf & bzimage modules In preparation for more incoming code that's either elf or bzimage specific, the respective blocks of functionality now sit in separate modules. This makes the code easier to navigate and improves readability. Fixes #22 as well. Signed-off-by: Alexandra Iordache --- src/loader/bzimage/mod.rs | 246 +++++++++ src/loader/{ => elf}/elf.rs | 0 src/loader/elf/mod.rs | 470 ++++++++++++++++ src/loader/{ => elf}/start_info.rs | 0 src/loader/{ => elf}/test_badnote.bin | Bin src/loader/{ => elf}/test_dummynote.bin | Bin src/loader/{ => elf}/test_elf.bin | Bin src/loader/{ => elf}/test_elfnote.bin | Bin src/loader/mod.rs | 696 ++---------------------- 9 files changed, 763 insertions(+), 649 deletions(-) create mode 100644 src/loader/bzimage/mod.rs rename src/loader/{ => elf}/elf.rs (100%) create mode 100644 src/loader/elf/mod.rs rename src/loader/{ => elf}/start_info.rs (100%) rename src/loader/{ => elf}/test_badnote.bin (100%) rename src/loader/{ => elf}/test_dummynote.bin (100%) rename src/loader/{ => elf}/test_elf.bin (100%) rename src/loader/{ => elf}/test_elfnote.bin (100%) diff --git a/src/loader/bzimage/mod.rs b/src/loader/bzimage/mod.rs new file mode 100644 index 00000000..d6aca6b2 --- /dev/null +++ b/src/loader/bzimage/mod.rs @@ -0,0 +1,246 @@ +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading bzimage kernels into guest memory. + +#![cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] + +use std::error::{self, Error as StdError}; +use std::fmt::{self, Display}; +use std::io::{Read, Seek, SeekFrom}; +use std::mem; + +use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; + +use super::{bootparam, Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; + +#[derive(Debug, PartialEq)] +/// Bzimage kernel loader errors. +pub enum Error { + /// Invalid bzImage binary. + InvalidBzImage, + /// Unable to read bzImage header. + ReadBzImageHeader, + /// Unable to read bzImage compressed image. + ReadBzImageCompressedKernel, + /// Unable to seek to bzImage end. + SeekBzImageEnd, + /// Unable to seek to bzImage header. + SeekBzImageHeader, + /// Unable to seek to bzImage compressed kernel. + SeekBzImageCompressedKernel, +} + +impl error::Error for Error { + fn description(&self) -> &str { + match self { + Error::InvalidBzImage => "Invalid bzImage", + Error::ReadBzImageHeader => "Unable to read bzImage header", + Error::ReadBzImageCompressedKernel => "Unable to read bzImage compressed kernel", + Error::SeekBzImageEnd => "Unable to seek bzImage end", + Error::SeekBzImageHeader => "Unable to seek bzImage header", + Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel", + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Kernel Loader Error: {}", self.description()) + } +} + +/// Big zImage (bzImage) kernel image support. +pub struct BzImage; + +impl KernelLoader for BzImage { + /// Loads a kernel from a bzImage to guest memory. + /// + /// The kernel is loaded at `code32_start`, the default load address stored in the bzImage + /// setup header. + /// + /// # Arguments + /// + /// * `guest_mem`: [`GuestMemory`] to load the kernel in. + /// * `kernel_start`: Address in guest memory where the kernel is loaded. + /// * `kernel_image` - Input bzImage image. + /// * `highmem_start_address`: Address where high memory starts. + /// + /// # Examples + /// + /// ```rust + /// # extern crate vm_memory; + /// # use linux_loader::loader::*; + /// # use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + /// # use std::io::Cursor; + /// let mem_size: usize = 0x1000000; + /// let himem_start = GuestAddress(0x0); + /// let kernel_addr = GuestAddress(0x200000); + /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); + /// let mut kernel_image = vec![]; + /// kernel_image.extend_from_slice(include_bytes!("bzimage")); + /// assert!(BzImage::load( + /// &gm, + /// Some(kernel_addr), + /// &mut Cursor::new(&kernel_image), + /// Some(himem_start), + /// ).is_ok()); + /// ``` + /// + /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html + fn load( + guest_mem: &M, + kernel_start: Option, + kernel_image: &mut F, + highmem_start_address: Option, + ) -> Result + where + F: Read + Seek, + { + let mut kernel_size = kernel_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekBzImageEnd)? as usize; + kernel_image + .seek(SeekFrom::Start(0x1F1)) + .map_err(|_| Error::SeekBzImageHeader)?; + + let mut boot_header = bootparam::setup_header::default(); + boot_header + .as_bytes() + .read_from(0, kernel_image, mem::size_of::()) + .map_err(|_| Error::ReadBzImageHeader)?; + + // If the `HdrS` magic number is not found at offset 0x202, the boot protocol version is + // "old", the image type is assumed as zImage, not bzImage. + if boot_header.header != 0x5372_6448 { + Err(Error::InvalidBzImage)?; + } + + // Follow the section related to loading the rest of the kernel in the linux boot protocol. + if (boot_header.version < 0x0200) || ((boot_header.loadflags & 0x1) == 0x0) { + Err(Error::InvalidBzImage)?; + } + + let mut setup_size = boot_header.setup_sects as usize; + if setup_size == 0 { + setup_size = 4; + } + setup_size = (setup_size + 1) * 512; + kernel_size -= setup_size; + + // Check that `code32_start`, the default address of the kernel, is not lower than high + // memory. + if (highmem_start_address.is_some()) + && (u64::from(boot_header.code32_start) < highmem_start_address.unwrap().raw_value()) + { + return Err(KernelLoaderError::InvalidKernelStartAddress); + } + + let mem_offset = match kernel_start { + Some(start) => start, + None => GuestAddress(u64::from(boot_header.code32_start)), + }; + + boot_header.code32_start = mem_offset.raw_value() as u32; + + let mut loader_result: KernelLoaderResult = Default::default(); + loader_result.setup_header = Some(boot_header); + loader_result.kernel_load = mem_offset; + + // Seek the compressed `vmlinux.bin` and read it to memory. + kernel_image + .seek(SeekFrom::Start(setup_size as u64)) + .map_err(|_| Error::SeekBzImageCompressedKernel)?; + guest_mem + .read_exact_from(mem_offset, kernel_image, kernel_size) + .map_err(|_| Error::ReadBzImageCompressedKernel)?; + + loader_result.kernel_end = mem_offset + .raw_value() + .checked_add(kernel_size as GuestUsize) + .ok_or(KernelLoaderError::MemoryOverflow)?; + + Ok(loader_result) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::io::Cursor; + use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + + const MEM_SIZE: u64 = 0x1000000; + + fn create_guest_mem() -> GuestMemoryMmap { + GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() + } + + fn make_bzimage() -> Vec { + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("bzimage")); + v + } + + #[allow(safe_packed_borrows)] + #[allow(non_snake_case)] + #[test] + fn test_load_bzImage() { + let gm = create_guest_mem(); + let image = make_bzimage(); + let mut kernel_start = GuestAddress(0x200000); + let mut highmem_start_address = GuestAddress(0x0); + + // load bzImage with good kernel_start and himem_start setting + let mut loader_result = BzImage::load( + &gm, + Some(kernel_start), + &mut Cursor::new(&image), + Some(highmem_start_address), + ) + .unwrap(); + + assert_eq!(loader_result.kernel_load.raw_value(), 0x200000); + assert_eq!(loader_result.setup_header.unwrap().header, 0x53726448); + assert_eq!(loader_result.setup_header.unwrap().version, 0x20d); + assert_eq!(loader_result.setup_header.unwrap().loadflags, 1); + assert_eq!(loader_result.kernel_end, 0x60c320); + + // load bzImage without kernel_start + loader_result = BzImage::load( + &gm, + None, + &mut Cursor::new(&image), + Some(highmem_start_address), + ) + .unwrap(); + assert_eq!(loader_result.kernel_load.raw_value(), 0x100000); + + // load bzImage withouth himem_start + loader_result = BzImage::load(&gm, None, &mut Cursor::new(&image), None).unwrap(); + assert_eq!(0x53726448, loader_result.setup_header.unwrap().header); + assert_eq!(loader_result.kernel_load.raw_value(), 0x100000); + + // load bzImage with a bad himem setting + kernel_start = GuestAddress(0x1000); + highmem_start_address = GuestAddress(0x200000); + + assert_eq!( + Some(KernelLoaderError::InvalidKernelStartAddress), + BzImage::load( + &gm, + Some(kernel_start), + &mut Cursor::new(&image), + Some(highmem_start_address), + ) + .err() + ); + } +} diff --git a/src/loader/elf.rs b/src/loader/elf/elf.rs similarity index 100% rename from src/loader/elf.rs rename to src/loader/elf/elf.rs diff --git a/src/loader/elf/mod.rs b/src/loader/elf/mod.rs new file mode 100644 index 00000000..62aa7b23 --- /dev/null +++ b/src/loader/elf/mod.rs @@ -0,0 +1,470 @@ +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading elf image kernels into guest memory. + +#![cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + +use std::error::{self, Error as StdError}; +use std::fmt::{self, Display}; +use std::io::{Read, Seek, SeekFrom}; +use std::mem; + +use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; + +use super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; + +#[allow(dead_code)] +#[allow(non_camel_case_types)] +#[allow(non_snake_case)] +#[allow(non_upper_case_globals)] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] +mod elf; + +#[allow(missing_docs)] +#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] +pub mod 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)] +/// Elf kernel loader errors. +pub enum Error { + /// Loaded big endian binary on a little endian platform. + BigEndianElfOnLittle, + /// Invalid ELF magic number + InvalidElfMagicNumber, + /// Invalid program header size. + InvalidProgramHeaderSize, + /// Invalid program header offset. + InvalidProgramHeaderOffset, + /// Invalid program header address. + InvalidProgramHeaderAddress, + /// Invalid entry address. + InvalidEntryAddress, + /// Unable to read ELF header. + ReadElfHeader, + /// Unable to read kernel image. + ReadKernelImage, + /// Unable to read program header. + ReadProgramHeader, + /// Unable to seek to kernel start. + SeekKernelStart, + /// Unable to seek to ELF start. + SeekElfStart, + /// Unable to seek to program header. + SeekProgramHeader, + /// Unable to seek to note header. + SeekNoteHeader, + /// Unable to read note header. + ReadNoteHeader, + /// Invalid PVH note. + InvalidPvhNote, +} + +impl error::Error for Error { + fn description(&self) -> &str { + match self { + 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::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", + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Kernel Loader Error: {}", self.description()) + } +} + +/// Raw ELF (a.k.a. vmlinux) kernel image support. +pub struct Elf; + +impl Elf { + fn validate_header(ehdr: &elf::Elf64_Ehdr) -> std::result::Result<(), Error> { + // 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::() { + return Err(Error::InvalidProgramHeaderSize); + } + if (ehdr.e_phoff as usize) < mem::size_of::() { + return Err(Error::InvalidProgramHeaderOffset); + } + Ok(()) + } +} + +impl KernelLoader for Elf { + /// Loads a kernel from a vmlinux elf image into guest memory. + /// + /// The kernel is loaded into guest memory at offset `phdr.p_paddr` specified by the elf image. + /// + /// # Arguments + /// + /// * `guest_mem`: [`GuestMemory`] to load the kernel in. + /// * `kernel_start`: Address in guest memory where the kernel is loaded. + /// * `kernel_image` - Input vmlinux image. + /// * `highmem_start_address`: Address where high memory starts. + /// + /// # Examples + /// + /// ```rust + /// # extern crate vm_memory; + /// # use linux_loader::loader::*; + /// # use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + /// # use std::io::Cursor; + /// let mem_size: usize = 0x1000000; + /// let himem_start = GuestAddress(0x0); + /// let kernel_addr = GuestAddress(0x200000); + /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); + /// let mut kernel_image = vec![]; + /// kernel_image.extend_from_slice(include_bytes!("test_elf.bin")); + /// assert!(Elf::load( + /// &gm, + /// Some(kernel_addr), + /// &mut Cursor::new(&kernel_image), + /// Some(himem_start), + /// ).is_ok()); + /// ``` + /// + /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html + fn load( + guest_mem: &M, + kernel_start: Option, + kernel_image: &mut F, + highmem_start_address: Option, + ) -> Result + 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::()) + .map_err(|_| Error::ReadElfHeader)?; + + // Sanity checks. + Self::validate_header(&ehdr)?; + if let Some(addr) = highmem_start_address { + if (ehdr.e_entry as u64) < addr.raw_value() { + Err(Error::InvalidEntryAddress)?; + } + } + + let mut loader_result: KernelLoaderResult = Default::default(); + + // Address where the kernel will be loaded. + loader_result.kernel_load = match kernel_start { + Some(start) => GuestAddress(start.raw_value() + (ehdr.e_entry as u64)), + 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::(); + let mut phdrs: Vec = 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); + } + + // 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; + } + + kernel_image + .seek(SeekFrom::Start(phdr.p_offset)) + .map_err(|_| Error::SeekKernelStart)?; + + // if the vmm does not specify where the kernel should be loaded, just + // load it to the physical address p_paddr for each segment. + let mem_offset = match kernel_start { + Some(start) => start + .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)?; + + loader_result.kernel_end = mem_offset + .raw_value() + .checked_add(phdr.p_memsz as GuestUsize) + .ok_or(KernelLoaderError::MemoryOverflow)?; + } + + // elf image has no setup_header which is defined for bzImage + loader_result.setup_header = None; + + Ok(loader_result) + } +} + +fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result> +where + F: Read + Seek, +{ + // Type of note header that encodes a 32-bit entry point address + // to boot a guest kernel using the PVH boot protocol. + const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18; + + 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; + let nhdr_sz = mem::size_of::(); + + while read_size < phdr.p_filesz as usize { + nhdr.as_bytes() + .read_from(0, kernel_image, nhdr_sz) + .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 == XEN_ELFNOTE_PHYS32_ENTRY { + break; + } + // Skip the note header plus the size of its fields (with alignment) + read_size += nhdr_sz + + align_up(u64::from(nhdr.n_namesz), n_align) + + align_up(u64::from(nhdr.n_descsz), 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(u64::from(nhdr.n_namesz), n_align) as i64, + )) + .map_err(|_| Error::SeekNoteHeader)?; + + // The PVH entry point is a 32-bit address, so the descriptor field + // must be capable of storing all such addresses. + if (nhdr.n_descsz as usize) < mem::size_of::() { + Err(Error::InvalidPvhNote)?; + } + + let mut pvh_addr_bytes = [0; mem::size_of::()]; + + // Read 32-bit address stored in the PVH note descriptor field. + kernel_image + .read_exact(&mut pvh_addr_bytes) + .map_err(|_| Error::ReadNoteHeader)?; + + Ok(Some(GuestAddress( + u32::from_le_bytes(pvh_addr_bytes).into(), + ))) +} + +/// Align address upwards. Taken from x86_64 crate: +/// https://docs.rs/x86_64/latest/x86_64/fn.align_up.html +/// +/// 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 tests { + use super::*; + use std::io::Cursor; + use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + + const MEM_SIZE: u64 = 0x1000000; + + fn create_guest_mem() -> GuestMemoryMmap { + GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() + } + + fn make_elf_bin() -> Vec { + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("test_elf.bin")); + v + } + + fn make_elfnote() -> Vec { + include_bytes!("test_elfnote.bin").to_vec() + } + + fn make_dummy_elfnote() -> Vec { + include_bytes!("test_dummynote.bin").to_vec() + } + + fn make_bad_elfnote() -> Vec { + include_bytes!("test_badnote.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() { + // Only little endian is supported. + 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() { + // 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(); + 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_entry_addr.unwrap().raw_value(), 0x1e1fe1f); + } + + #[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!(loader_result.pvh_entry_addr.is_none()); + } + + #[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() + ); + } +} diff --git a/src/loader/start_info.rs b/src/loader/elf/start_info.rs similarity index 100% rename from src/loader/start_info.rs rename to src/loader/elf/start_info.rs diff --git a/src/loader/test_badnote.bin b/src/loader/elf/test_badnote.bin similarity index 100% rename from src/loader/test_badnote.bin rename to src/loader/elf/test_badnote.bin diff --git a/src/loader/test_dummynote.bin b/src/loader/elf/test_dummynote.bin similarity index 100% rename from src/loader/test_dummynote.bin rename to src/loader/elf/test_dummynote.bin diff --git a/src/loader/test_elf.bin b/src/loader/elf/test_elf.bin similarity index 100% rename from src/loader/test_elf.bin rename to src/loader/elf/test_elf.bin diff --git a/src/loader/test_elfnote.bin b/src/loader/elf/test_elfnote.bin similarity index 100% rename from src/loader/test_elfnote.bin rename to src/loader/elf/test_elfnote.bin diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 8c299e7a..4e8fe579 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -18,16 +18,10 @@ extern crate vm_memory; -use std::error::{self, Error as KernelLoaderError}; +use std::error::Error as StdError; use std::ffi::CStr; use std::fmt::{self, Display}; -#[cfg(any(feature = "elf", feature = "bzimage"))] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use std::io::SeekFrom; use std::io::{Read, Seek}; -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -use std::mem; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; @@ -39,107 +33,58 @@ use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsiz #[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] pub mod bootparam; -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -#[allow(missing_docs)] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] -pub mod start_info; +#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +pub mod elf; -#[allow(dead_code)] -#[allow(non_camel_case_types)] -#[allow(non_snake_case)] -#[allow(non_upper_case_globals)] -#[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] -mod elf; +#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] +pub mod bzimage; + +#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +pub use elf::Elf; +#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +pub use elf::Error as ElfError; + +#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] +pub use bzimage::BzImage; +#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] +pub use bzimage::Error as BzImageError; #[derive(Debug, PartialEq)] /// Kernel loader errors. pub enum Error { - /// Loaded big endian binary on a little endian platform. - BigEndianElfOnLittle, + /// Failed to load bzimage. + #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] + Bzimage(BzImageError), + + /// Failed to load elf image. + #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + Elf(ElfError), + /// Failed writing command line to guest memory. CommandLineCopy, /// Command line overflowed guest memory. CommandLineOverflow, - /// Invalid ELF magic number - InvalidElfMagicNumber, - /// Invalid program header size. - InvalidProgramHeaderSize, - /// Invalid program header offset. - InvalidProgramHeaderOffset, - /// Invalid program header address. - InvalidProgramHeaderAddress, - /// Invalid entry address. - InvalidEntryAddress, - /// Invalid bzImage binary. - InvalidBzImage, /// Invalid kernel start address. InvalidKernelStartAddress, /// Memory to load kernel image is too small. MemoryOverflow, - /// Unable to read ELF header. - ReadElfHeader, - /// Unable to read kernel image. - ReadKernelImage, - /// Unable to read program header. - ReadProgramHeader, - /// Unable to read bzImage header. - ReadBzImageHeader, - /// Unable to read bzImage compressed image. - ReadBzImageCompressedKernel, - /// Unable to seek to kernel start. - SeekKernelStart, - /// Unable to seek to ELF start. - SeekElfStart, - /// Unable to seek to program header. - SeekProgramHeader, - /// Unable to seek to bzImage end. - SeekBzImageEnd, - /// Unable to seek to bzImage header. - SeekBzImageHeader, - /// Unable to seek to bzImage compressed kernel. - SeekBzImageCompressedKernel, - /// Unable to seek to note header. - SeekNoteHeader, - /// Unable to read note header. - ReadNoteHeader, - /// Invalid PVH note. - InvalidPvhNote, } /// A specialized `Result` type for the kernel loader. pub type Result = std::result::Result; -impl error::Error for Error { +impl StdError for Error { fn description(&self) -> &str { match self { - Error::BigEndianElfOnLittle => { - "Trying to load big-endian binary on little-endian machine" - } + #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] + Error::Bzimage(ref e) => e.description(), + #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] + Error::Elf(ref e) => e.description(), + Error::CommandLineCopy => "Failed writing command line to guest memory", Error::CommandLineOverflow => "Command line overflowed guest memory", - 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::InvalidBzImage => "Invalid bzImage", Error::InvalidKernelStartAddress => "Invalid kernel start address", Error::MemoryOverflow => "Memory to load kernel image is not enough", - Error::ReadElfHeader => "Unable to read elf header", - Error::ReadKernelImage => "Unable to read kernel image", - Error::ReadProgramHeader => "Unable to read program header", - Error::ReadBzImageHeader => "Unable to read bzImage header", - Error::ReadBzImageCompressedKernel => "Unable to read bzImage compressed kernel", - 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::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", - Error::InvalidPvhNote => "Invalid PVH note header", } } } @@ -150,12 +95,26 @@ impl Display for Error { } } -#[derive(Debug, Default, Copy, Clone, PartialEq)] -/// Result of the KernelLoader load() call. +#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] +impl From for Error { + fn from(err: ElfError) -> Self { + Error::Elf(err) + } +} + +#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] +impl From for Error { + fn from(err: BzImageError) -> Self { + Error::Bzimage(err) + } +} + +/// Result of [`KernelLoader.load()`](trait.KernelLoader.html#tymethod.load). /// /// This specifies where the kernel is loading and passes additional /// information for the rest of the boot process to be completed by /// the VMM. +#[derive(Default)] pub struct KernelLoaderResult { /// Address in the guest memory where the kernel image starts to be loaded pub kernel_load: GuestAddress, @@ -185,313 +144,9 @@ pub trait KernelLoader { F: Read + Seek; } -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -/// Raw ELF (a.k.a. vmlinux) kernel image support. -pub struct Elf; - -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -unsafe impl ByteValued for elf::Elf64_Ehdr {} -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -unsafe impl ByteValued for elf::Elf64_Nhdr {} -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -unsafe impl ByteValued for elf::Elf64_Phdr {} - unsafe impl ByteValued for bootparam::setup_header {} -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl KernelLoader for Elf { - /// Loads a kernel from a vmlinux elf image to a slice - /// - /// kernel is loaded into guest memory at offset phdr.p_paddr specified by elf image. - /// - /// # 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. - /// * `highmem_start_address` - This is the start of the high memory, kernel should above it. - /// - /// # Returns - /// * KernelLoaderResult - fn load( - guest_mem: &M, - kernel_start: Option, - kernel_image: &mut F, - highmem_start_address: Option, - ) -> Result - 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::()) - .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::() { - return Err(Error::InvalidProgramHeaderSize); - } - if (ehdr.e_phoff as usize) < mem::size_of::() { - return Err(Error::InvalidProgramHeaderOffset); - } - if (highmem_start_address.is_some()) - && ((ehdr.e_entry as u64) < highmem_start_address.unwrap().raw_value()) - { - return Err(Error::InvalidEntryAddress); - } - - let mut loader_result: KernelLoaderResult = Default::default(); - // where the kernel will be start loaded. - loader_result.kernel_load = match kernel_start { - Some(start) => GuestAddress(start.raw_value() + (ehdr.e_entry as u64)), - 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::(); - let mut phdrs: Vec = 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); - } - - // 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; - } - - kernel_image - .seek(SeekFrom::Start(phdr.p_offset)) - .map_err(|_| Error::SeekKernelStart)?; - - // if the vmm does not specify where the kernel should be loaded, just - // load it to the physical address p_paddr for each segment. - let mem_offset = match kernel_start { - Some(start) => start - .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)?; - - loader_result.kernel_end = mem_offset - .raw_value() - .checked_add(phdr.p_memsz as GuestUsize) - .ok_or(Error::MemoryOverflow)?; - } - - // elf image has no setup_header which is defined for bzImage - loader_result.setup_header = None; - - Ok(loader_result) - } -} - -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result> -where - F: Read + Seek, -{ - // Type of note header that encodes a 32-bit entry point address - // to boot a guest kernel using the PVH boot protocol. - const XEN_ELFNOTE_PHYS32_ENTRY: u32 = 18; - - 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; - let nhdr_sz = mem::size_of::(); - - while read_size < phdr.p_filesz as usize { - nhdr.as_bytes() - .read_from(0, kernel_image, nhdr_sz) - .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 == XEN_ELFNOTE_PHYS32_ENTRY { - break; - } - // Skip the note header plus the size of its fields (with alignment) - read_size += nhdr_sz - + align_up(u64::from(nhdr.n_namesz), n_align) - + align_up(u64::from(nhdr.n_descsz), 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(u64::from(nhdr.n_namesz), n_align) as i64, - )) - .map_err(|_| Error::SeekNoteHeader)?; - - // The PVH entry point is a 32-bit address, so the descriptor field - // must be capable of storing all such addresses. - if (nhdr.n_descsz as usize) < mem::size_of::() { - return Err(Error::InvalidPvhNote); - } - - let mut pvh_addr_bytes = [0; mem::size_of::()]; - - // Read 32-bit address stored in the PVH note descriptor field. - kernel_image - .read_exact(&mut pvh_addr_bytes) - .map_err(|_| Error::ReadNoteHeader)?; - - Ok(Some(GuestAddress( - u32::from_le_bytes(pvh_addr_bytes).into(), - ))) -} - -#[cfg(feature = "bzimage")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -/// Big zImage (bzImage) kernel image support. -pub struct BzImage; - -#[cfg(feature = "bzimage")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -impl KernelLoader for BzImage { - /// Loads a bzImage - /// - /// kernel is loaded into guest memory at code32_start the default load address - /// stored in bzImage setup header. - /// - /// # Arguments - /// - /// * `guest_mem` - The guest memory where the kernel image is loaded. - /// * `kernel_start` - The offset into 'guest_mem' at which to load the kernel. - /// * `kernel_image` - Input bzImage image. - /// * `highmem_start_address` - This is the start of the high memory, kernel should above it. - /// - /// # Returns - /// * KernelLoaderResult - fn load( - guest_mem: &M, - kernel_start: Option, - kernel_image: &mut F, - highmem_start_address: Option, - ) -> Result - where - F: Read + Seek, - { - let mut kernel_size = kernel_image - .seek(SeekFrom::End(0)) - .map_err(|_| Error::SeekBzImageEnd)? as usize; - kernel_image - .seek(SeekFrom::Start(0x1F1)) - .map_err(|_| Error::SeekBzImageHeader)?; - - let mut boot_header = bootparam::setup_header::default(); - boot_header - .as_bytes() - .read_from(0, kernel_image, mem::size_of::()) - .map_err(|_| Error::ReadBzImageHeader)?; - - // if the HdrS magic number is not found at offset 0x202, the boot protocol version is "old", - // the image type is assumed as zImage, not bzImage. - if boot_header.header != 0x5372_6448 { - return Err(Error::InvalidBzImage); - } - - // follow section of loading the rest of the kernel in linux boot protocol - if (boot_header.version < 0x0200) || ((boot_header.loadflags & 0x1) == 0x0) { - return Err(Error::InvalidBzImage); - } - - let mut setup_size = boot_header.setup_sects as usize; - if setup_size == 0 { - setup_size = 4; - } - setup_size = (setup_size + 1) * 512; - kernel_size -= setup_size; - - // verify bzImage validation by checking if code32_start, the defaults to the address of - // the kernel is not lower than high memory. - if (highmem_start_address.is_some()) - && (u64::from(boot_header.code32_start) < highmem_start_address.unwrap().raw_value()) - { - return Err(Error::InvalidKernelStartAddress); - } - - let mem_offset = match kernel_start { - Some(start) => start, - None => GuestAddress(u64::from(boot_header.code32_start)), - }; - - boot_header.code32_start = mem_offset.raw_value() as u32; - - let mut loader_result: KernelLoaderResult = Default::default(); - loader_result.setup_header = Some(boot_header); - loader_result.kernel_load = mem_offset; - - //seek the compressed vmlinux.bin and read to memory - kernel_image - .seek(SeekFrom::Start(setup_size as u64)) - .map_err(|_| Error::SeekBzImageCompressedKernel)?; - guest_mem - .read_exact_from(mem_offset, kernel_image, kernel_size) - .map_err(|_| Error::ReadBzImageCompressedKernel)?; - - loader_result.kernel_end = mem_offset - .raw_value() - .checked_add(kernel_size as GuestUsize) - .ok_or(Error::MemoryOverflow)?; - - Ok(loader_result) - } -} - -/// Writes the command line string to the given memory slice. +/// Writes the command line string to the given guest memory slice. /// /// # Arguments /// @@ -522,29 +177,9 @@ pub fn load_cmdline( Ok(()) } -/// Align address upwards. Taken from x86_64 crate: -/// https://docs.rs/x86_64/latest/x86_64/fn.align_up.html -/// -/// Returns the smallest x with alignment `align` so that x >= addr. The alignment must be -/// a power of 2. -#[cfg(feature = "elf")] -#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] -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 { +mod tests { use super::*; - #[cfg(any(feature = "elf", feature = "bzimage"))] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - use std::io::Cursor; use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; const MEM_SIZE: u64 = 0x1000000; @@ -553,199 +188,6 @@ mod test { GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() } - #[cfg(feature = "bzimage")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn make_bzimage() -> Vec { - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("bzimage")); - v - } - - // Elf64 image that prints hello world on x86_64. - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn make_elf_bin() -> Vec { - let mut v = Vec::new(); - v.extend_from_slice(include_bytes!("test_elf.bin")); - v - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn make_elfnote() -> Vec { - include_bytes!("test_elfnote.bin").to_vec() - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn make_dummy_elfnote() -> Vec { - include_bytes!("test_dummynote.bin").to_vec() - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn make_bad_elfnote() -> Vec { - include_bytes!("test_badnote.bin").to_vec() - } - - #[allow(safe_packed_borrows)] - #[allow(non_snake_case)] - #[test] - #[cfg(feature = "bzimage")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn load_bzImage() { - let gm = create_guest_mem(); - let image = make_bzimage(); - let mut kernel_start = GuestAddress(0x200000); - let mut highmem_start_address = GuestAddress(0x0); - - // load bzImage with good kernel_start and himem_start setting - let mut loader_result = BzImage::load( - &gm, - Some(kernel_start), - &mut Cursor::new(&image), - Some(highmem_start_address), - ) - .unwrap(); - assert_eq!(0x53726448, loader_result.setup_header.unwrap().header); - println!( - "bzImage is loaded at {:8x} \n", - loader_result.kernel_load.raw_value() - ); - println!( - "bzImage version is {:2x} \n", - loader_result.setup_header.unwrap().version - ); - println!( - "bzImage loadflags is {:x} \n", - loader_result.setup_header.unwrap().loadflags - ); - println!( - "bzImage kernel size is {:4x} \n", - (loader_result.kernel_end as u32) - ); - - // load bzImage without kernel_start - loader_result = BzImage::load( - &gm, - None, - &mut Cursor::new(&image), - Some(highmem_start_address), - ) - .unwrap(); - assert_eq!(0x53726448, loader_result.setup_header.unwrap().header); - println!( - "bzImage is loaded at {:8x} \n", - loader_result.kernel_load.raw_value() - ); - - // load bzImage withouth himem_start - loader_result = BzImage::load(&gm, None, &mut Cursor::new(&image), None).unwrap(); - assert_eq!(0x53726448, loader_result.setup_header.unwrap().header); - println!( - "bzImage is loaded at {:8x} \n", - loader_result.kernel_load.raw_value() - ); - - // load bzImage with a bad himem setting - kernel_start = GuestAddress(0x1000); - highmem_start_address = GuestAddress(0x200000); - let x = BzImage::load( - &gm, - Some(kernel_start), - &mut Cursor::new(&image), - Some(highmem_start_address), - ); - assert_eq!(x.is_ok(), false); - println!("load bzImage with bad himem setting \n"); - } - - #[test] - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn 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(); - println!( - "load elf at address {:8x} \n", - loader_result.kernel_load.raw_value() - ); - - loader_result = Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); - println!( - "load elf at address {:8x} \n", - loader_result.kernel_load.raw_value() - ); - - loader_result = Elf::load( - &gm, - None, - &mut Cursor::new(&image), - Some(highmem_start_address), - ) - .unwrap(); - println!( - "load elf at address {:8x} \n", - loader_result.kernel_load.raw_value() - ); - - highmem_start_address = GuestAddress(0xa00000); - assert_eq!( - Err(Error::InvalidEntryAddress), - Elf::load( - &gm, - None, - &mut Cursor::new(&image), - Some(highmem_start_address) - ) - ); - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - #[test] - fn 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(); - println!( - "PVH entry point at address {:8x} \n", - loader_result.pvh_entry_addr.unwrap().raw_value() - ); - assert_eq!(loader_result.pvh_entry_addr.unwrap().raw_value(), 0x1e1fe1f); - } - - #[test] - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn dumy_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!(loader_result.pvh_entry_addr.is_none()); - } - - #[test] - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - fn bad_elfnote() { - let gm = create_guest_mem(); - let badnote_image = make_bad_elfnote(); - assert_eq!( - Err(Error::InvalidPvhNote), - Elf::load(&gm, None, &mut Cursor::new(&badnote_image), None) - ); - } - #[test] fn cmdline_overflow() { let gm = create_guest_mem(); @@ -787,48 +229,4 @@ mod test { let val: u8 = gm.read_obj(cmdline_address).unwrap(); assert_eq!(val, '\0' as u8); } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - #[test] - fn bad_magic() { - let gm = create_guest_mem(); - let kernel_addr = GuestAddress(0x0); - let mut bad_image = make_elf_bin(); - bad_image[0x1] = 0x33; - assert_eq!( - Err(Error::InvalidElfMagicNumber), - Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None) - ); - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - #[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(); - bad_image[0x5] = 2; - assert_eq!( - Err(Error::BigEndianElfOnLittle), - Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None) - ); - } - - #[cfg(feature = "elf")] - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] - #[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(); - bad_image[0x20] = 0x10; - assert_eq!( - Err(Error::InvalidProgramHeaderOffset), - Elf::load(&gm, Some(kernel_addr), &mut Cursor::new(&bad_image), None) - ); - } } From 63fceb9491525439aff835b509a612082eb8c11a Mon Sep 17 00:00:00 2001 From: Alexandra Iordache Date: Tue, 10 Mar 2020 14:11:33 +0200 Subject: [PATCH 2/4] add x86_64 and aarch64 modules Phase 2 of code reorganization: separate x86_64 from aarch64 functionality in modules. Signed-off-by: Alexandra Iordache --- coverage_config_aarch64.json | 2 +- coverage_config_x86_64.json | 2 +- src/loader/aarch64/mod.rs | 12 ++++++ src/loader/mod.rs | 38 ++++++++---------- src/loader/{ => x86_64}/bzimage/mod.rs | 8 ++-- src/loader/{ => x86_64}/elf/elf.rs | 0 src/loader/{ => x86_64}/elf/mod.rs | 38 +++++++++--------- src/loader/{ => x86_64}/elf/start_info.rs | 0 src/loader/{ => x86_64}/elf/test_badnote.bin | Bin .../{ => x86_64}/elf/test_dummynote.bin | Bin src/loader/{ => x86_64}/elf/test_elf.bin | Bin src/loader/{ => x86_64}/elf/test_elfnote.bin | Bin src/loader/x86_64/mod.rs | 18 +++++++++ 13 files changed, 73 insertions(+), 45 deletions(-) create mode 100644 src/loader/aarch64/mod.rs rename src/loader/{ => x86_64}/bzimage/mod.rs (98%) rename src/loader/{ => x86_64}/elf/elf.rs (100%) rename src/loader/{ => x86_64}/elf/mod.rs (95%) rename src/loader/{ => x86_64}/elf/start_info.rs (100%) rename src/loader/{ => x86_64}/elf/test_badnote.bin (100%) rename src/loader/{ => x86_64}/elf/test_dummynote.bin (100%) rename src/loader/{ => x86_64}/elf/test_elf.bin (100%) rename src/loader/{ => x86_64}/elf/test_elfnote.bin (100%) create mode 100644 src/loader/x86_64/mod.rs diff --git a/coverage_config_aarch64.json b/coverage_config_aarch64.json index b1b035d8..861b91e6 100644 --- a/coverage_config_aarch64.json +++ b/coverage_config_aarch64.json @@ -1,5 +1,5 @@ { - "coverage_score": 71.4, + "coverage_score": 74.1, "exclude_path": "", "crate_features": "" } diff --git a/coverage_config_x86_64.json b/coverage_config_x86_64.json index 27587e41..27f86931 100644 --- a/coverage_config_x86_64.json +++ b/coverage_config_x86_64.json @@ -1,5 +1,5 @@ { - "coverage_score": 77.1, + "coverage_score": 74.7, "exclude_path": "", "crate_features": "" } diff --git a/src/loader/aarch64/mod.rs b/src/loader/aarch64/mod.rs new file mode 100644 index 00000000..e5105c03 --- /dev/null +++ b/src/loader/aarch64/mod.rs @@ -0,0 +1,12 @@ +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading `aarch64` kernels into guest memory. + +#![cfg(target_arch = "aarch64")] diff --git a/src/loader/mod.rs b/src/loader/mod.rs index 4e8fe579..ba30eff5 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -9,7 +9,7 @@ // // SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause -//! Traits and Structs +//! Traits and Structs for loading kernels into guest memory. //! - [KernelLoader](trait.KernelLoader.html): load kernel image into guest memory //! - [KernelLoaderResult](struct.KernelLoaderResult.html): the structure which loader //! returns to VMM to assist zero page construction and boot environment setup @@ -33,32 +33,26 @@ use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsiz #[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] pub mod bootparam; -#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] -pub mod elf; - -#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] -pub mod bzimage; - -#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] -pub use elf::Elf; -#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] -pub use elf::Error as ElfError; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +mod x86_64; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +pub use x86_64::*; -#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] -pub use bzimage::BzImage; -#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] -pub use bzimage::Error as BzImageError; +#[cfg(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use aarch64::*; #[derive(Debug, PartialEq)] /// Kernel loader errors. pub enum Error { /// Failed to load bzimage. #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] - Bzimage(BzImageError), + Bzimage(bzimage::Error), /// Failed to load elf image. #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] - Elf(ElfError), + Elf(elf::Error), /// Failed writing command line to guest memory. CommandLineCopy, @@ -96,15 +90,15 @@ impl Display for Error { } #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] -impl From for Error { - fn from(err: ElfError) -> Self { +impl From for Error { + fn from(err: elf::Error) -> Self { Error::Elf(err) } } #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))] -impl From for Error { - fn from(err: BzImageError) -> Self { +impl From for Error { + fn from(err: bzimage::Error) -> Self { Error::Bzimage(err) } } @@ -114,7 +108,7 @@ impl From for Error { /// This specifies where the kernel is loading and passes additional /// information for the rest of the boot process to be completed by /// the VMM. -#[derive(Default)] +#[derive(Clone, Copy, Debug, Default, PartialEq)] pub struct KernelLoaderResult { /// Address in the guest memory where the kernel image starts to be loaded pub kernel_load: GuestAddress, diff --git a/src/loader/bzimage/mod.rs b/src/loader/x86_64/bzimage/mod.rs similarity index 98% rename from src/loader/bzimage/mod.rs rename to src/loader/x86_64/bzimage/mod.rs index d6aca6b2..dc37f9d3 100644 --- a/src/loader/bzimage/mod.rs +++ b/src/loader/x86_64/bzimage/mod.rs @@ -18,7 +18,9 @@ use std::mem; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; -use super::{bootparam, Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; +use super::super::{ + bootparam, Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result, +}; #[derive(Debug, PartialEq)] /// Bzimage kernel loader errors. @@ -85,12 +87,12 @@ impl KernelLoader for BzImage { /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); /// let mut kernel_image = vec![]; /// kernel_image.extend_from_slice(include_bytes!("bzimage")); - /// assert!(BzImage::load( + /// bzimage::BzImage::load( /// &gm, /// Some(kernel_addr), /// &mut Cursor::new(&kernel_image), /// Some(himem_start), - /// ).is_ok()); + /// ).unwrap(); /// ``` /// /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html diff --git a/src/loader/elf/elf.rs b/src/loader/x86_64/elf/elf.rs similarity index 100% rename from src/loader/elf/elf.rs rename to src/loader/x86_64/elf/elf.rs diff --git a/src/loader/elf/mod.rs b/src/loader/x86_64/elf/mod.rs similarity index 95% rename from src/loader/elf/mod.rs rename to src/loader/x86_64/elf/mod.rs index 62aa7b23..905a68b4 100644 --- a/src/loader/elf/mod.rs +++ b/src/loader/x86_64/elf/mod.rs @@ -18,7 +18,7 @@ use std::mem; use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; -use super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; +use super::super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; #[allow(dead_code)] #[allow(non_camel_case_types)] @@ -151,12 +151,12 @@ impl KernelLoader for Elf { /// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap(); /// let mut kernel_image = vec![]; /// kernel_image.extend_from_slice(include_bytes!("test_elf.bin")); - /// assert!(Elf::load( + /// elf::Elf::load( /// &gm, /// Some(kernel_addr), /// &mut Cursor::new(&kernel_image), /// Some(himem_start), - /// ).is_ok()); + /// ).unwrap(); /// ``` /// /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html @@ -182,7 +182,7 @@ impl KernelLoader for Elf { Self::validate_header(&ehdr)?; if let Some(addr) = highmem_start_address { if (ehdr.e_entry as u64) < addr.raw_value() { - Err(Error::InvalidEntryAddress)?; + return Err(Error::InvalidEntryAddress.into()); } } @@ -252,19 +252,19 @@ fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result(); @@ -274,12 +274,12 @@ where .read_from(0, kernel_image, nhdr_sz) .map_err(|_| Error::ReadNoteHeader)?; - // If the note header found is not the desired one, keep reading until - // the end of the segment + // If the note header found is not the desired one, keep reading until the end of the + // segment. if nhdr.n_type == XEN_ELFNOTE_PHYS32_ENTRY { break; } - // Skip the note header plus the size of its fields (with alignment) + // Skip the note header plus the size of its fields (with alignment). read_size += nhdr_sz + align_up(u64::from(nhdr.n_namesz), n_align) + align_up(u64::from(nhdr.n_descsz), n_align); @@ -290,21 +290,23 @@ where } if read_size >= phdr.p_filesz as usize { - return Ok(None); // PVH ELF note not found, nothing else to do. + // PVH ELF note not found, nothing else to do. + return Ok(None); } + // 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. + // 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(u64::from(nhdr.n_namesz), n_align) as i64, )) .map_err(|_| Error::SeekNoteHeader)?; - // The PVH entry point is a 32-bit address, so the descriptor field - // must be capable of storing all such addresses. + // The PVH entry point is a 32-bit address, so the descriptor field must be capable of storing + // all such addresses. if (nhdr.n_descsz as usize) < mem::size_of::() { - Err(Error::InvalidPvhNote)?; + return Err(Error::InvalidPvhNote.into()); } let mut pvh_addr_bytes = [0; mem::size_of::()]; diff --git a/src/loader/elf/start_info.rs b/src/loader/x86_64/elf/start_info.rs similarity index 100% rename from src/loader/elf/start_info.rs rename to src/loader/x86_64/elf/start_info.rs diff --git a/src/loader/elf/test_badnote.bin b/src/loader/x86_64/elf/test_badnote.bin similarity index 100% rename from src/loader/elf/test_badnote.bin rename to src/loader/x86_64/elf/test_badnote.bin diff --git a/src/loader/elf/test_dummynote.bin b/src/loader/x86_64/elf/test_dummynote.bin similarity index 100% rename from src/loader/elf/test_dummynote.bin rename to src/loader/x86_64/elf/test_dummynote.bin diff --git a/src/loader/elf/test_elf.bin b/src/loader/x86_64/elf/test_elf.bin similarity index 100% rename from src/loader/elf/test_elf.bin rename to src/loader/x86_64/elf/test_elf.bin diff --git a/src/loader/elf/test_elfnote.bin b/src/loader/x86_64/elf/test_elfnote.bin similarity index 100% rename from src/loader/elf/test_elfnote.bin rename to src/loader/x86_64/elf/test_elfnote.bin diff --git a/src/loader/x86_64/mod.rs b/src/loader/x86_64/mod.rs new file mode 100644 index 00000000..1ad58b3f --- /dev/null +++ b/src/loader/x86_64/mod.rs @@ -0,0 +1,18 @@ +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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-BSD-3-Clause file. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for loading `x86_64` kernels into guest memory. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] + +#[cfg(feature = "elf")] +pub mod elf; + +#[cfg(feature = "bzimage")] +pub mod bzimage; From 664b3dab76d7d242e514fd3037a85d68ba849a43 Mon Sep 17 00:00:00 2001 From: Alexandra Iordache Date: Tue, 10 Mar 2020 14:42:35 +0200 Subject: [PATCH 3/4] buildkite: update hook for new bzimage location Signed-off-by: Alexandra Iordache --- .buildkite/hooks/post-checkout | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.buildkite/hooks/post-checkout b/.buildkite/hooks/post-checkout index 05440cab..242e8546 100644 --- a/.buildkite/hooks/post-checkout +++ b/.buildkite/hooks/post-checkout @@ -13,8 +13,6 @@ mkdir -p ${EXTRACT_PATH} wget $DEB_URL -P ${REPO_PATH} dpkg-deb -x ${DEB_PATH} ${EXTRACT_PATH} - -mv ${BZIMAGE_PATH} ${REPO_PATH}/src/loader/bzimage +mv ${BZIMAGE_PATH} ${REPO_PATH}/src/loader/x86_64/bzimage/bzimage rm -r ${EXTRACT_PATH} rm -f ${DEB_PATH} - From 388fd99c48b96569425dfd793475cd9bf74e035e Mon Sep 17 00:00:00 2001 From: Alexandra Iordache Date: Wed, 18 Mar 2020 10:06:04 +0200 Subject: [PATCH 4/4] elf: add missing documentation Signed-off-by: Alexandra Iordache --- src/loader/x86_64/elf/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/loader/x86_64/elf/mod.rs b/src/loader/x86_64/elf/mod.rs index 905a68b4..347e1c72 100644 --- a/src/loader/x86_64/elf/mod.rs +++ b/src/loader/x86_64/elf/mod.rs @@ -104,6 +104,7 @@ impl Display for Error { pub struct Elf; impl Elf { + /// Verifies that magic numbers are present in the Elf header. fn validate_header(ehdr: &elf::Elf64_Ehdr) -> std::result::Result<(), Error> { // Sanity checks if ehdr.e_ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8 @@ -248,6 +249,12 @@ impl KernelLoader for Elf { } } +/// Examines a supplied elf program header of type `PT_NOTE` to determine if it contains an entry +/// of type `XEN_ELFNOTE_PHYS32_ENTRY` (0x12). Notes of this type encode a physical 32-bit entry +/// point address into the kernel, which is used when launching guests in 32-bit (protected) mode +/// with paging disabled, as described by the PVH boot protocol. +/// Returns the encoded entry point address, or `None` if no `XEN_ELFNOTE_PHYS32_ENTRY` entries are +/// found in the note header. fn parse_elf_note(phdr: &elf::Elf64_Phdr, kernel_image: &mut F) -> Result> where F: Read + Seek,