diff --git a/Cargo.toml b/Cargo.toml index b6a3a0d4..b5a2fa0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,9 +6,10 @@ edition = "2018" license = "Apache-2.0 AND BSD-3-Clause" [features] -default = ["elf"] +default = ["elf", "pe"] elf = [] bzimage = [] +pe = [] [dependencies] vm-memory = {version = ">=0.2.0", features = ["backend-mmap"]} diff --git a/README.md b/README.md index 939eebc0..b4f4b9fd 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,9 @@ ## Short-description -* Parsing and loading vmlinux (raw ELF image) and bzImage images +* Parsing and loading vmlinux (raw ELF image), bzImage and PE images * Linux command line parsing and generation +* Loading device tree blobs * Definitions and helpers for the Linux boot protocol ## How to build @@ -44,7 +45,7 @@ locally build a bzImage, copy it to the `src/loader` directory and run # Assuming your linux-loader and linux-stable are both under ${LINUX_LOADER}: git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ${LINUX_LOADER}/linux-stable cd linux-stable -make bzImage +make bzImage cp linux-stable/arch/x86/boot/bzImage ${LINUX_LOADER}/linux-loader/src/loader/ cd ${LINUX_LOADER}/linux-loader container_version=5 @@ -53,5 +54,5 @@ docker run -it \ --volume $(pwd):/linux-loader \ rustvmm/dev:v${container_version} cd linux-loader/ -cargo test +cargo test ``` diff --git a/coverage_config_aarch64.json b/coverage_config_aarch64.json index 861b91e6..792cfa39 100644 --- a/coverage_config_aarch64.json +++ b/coverage_config_aarch64.json @@ -1,5 +1,5 @@ { - "coverage_score": 74.1, + "coverage_score": 75.7, "exclude_path": "", "crate_features": "" } diff --git a/src/lib.rs b/src/lib.rs index fadbd490..6d0f6896 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,20 +11,16 @@ //! A Linux kernel image loading crate. //! -//! This crate offers support for loading raw ELF (vmlinux) and compressed -//! big zImage (bzImage) kernel images. +//! This crate offers support for loading raw ELF (vmlinux), compressed +//! big zImage (bzImage) and PE (Image) kernel images. //! Support for any other kernel image format can be added by implementing //! the KernelLoader. //! //! # Platform support //! //! - x86_64 +//! - ARM64 //! -//! This crates only supports x86_64 platforms because it implements support -//! for kernel image formats (vmlinux and bzImage) that are x86 specific. -//! -//! Extending it to support other kernel image formats (e.g. ARM's Image) -//! will make it consumable by other platforms. pub mod cmdline; pub mod loader; diff --git a/src/loader/aarch64/mod.rs b/src/loader/aarch64/mod.rs index e5105c03..19acadde 100644 --- a/src/loader/aarch64/mod.rs +++ b/src/loader/aarch64/mod.rs @@ -10,3 +10,6 @@ //! Traits and structs for loading `aarch64` kernels into guest memory. #![cfg(target_arch = "aarch64")] + +#[cfg(feature = "pe")] +pub mod pe; diff --git a/src/loader/aarch64/pe/mod.rs b/src/loader/aarch64/pe/mod.rs new file mode 100644 index 00000000..feae0bc8 --- /dev/null +++ b/src/loader/aarch64/pe/mod.rs @@ -0,0 +1,228 @@ +// 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 pe image kernels into guest memory. + +#![cfg(feature = "pe")] + +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::super::{Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result}; + +/// ARM64 Image (PE) format support +pub struct PE; + +unsafe impl ByteValued for arm64_image_header {} + +#[derive(Debug, PartialEq)] +/// PE kernel loader errors. +pub enum Error { + /// Unable to seek to Image end. + SeekImageEnd, + /// Unable to seek to Image header. + SeekImageHeader, + /// Unable to seek to DTB start. + SeekDtbStart, + /// Unable to seek to DTB end. + SeekDtbEnd, + /// Device tree binary too big. + DtbTooBig, + /// Unable to read kernel image. + ReadKernelImage, + /// Unable to read Image header. + ReadImageHeader, + /// Unable to read DTB image + ReadDtbImage, + /// Invalid Image binary. + InvalidImage, + /// Invalid Image magic number. + InvalidImageMagicNumber, +} + +impl error::Error for Error { + fn description(&self) -> &str { + match self { + Error::SeekImageEnd => "Unable to seek Image end", + Error::SeekImageHeader => "Unable to seek Image header", + Error::ReadImageHeader => "Unable to read Image header", + Error::ReadDtbImage => "Unable to read DTB image", + Error::SeekDtbStart => "Unable to seek DTB start", + Error::SeekDtbEnd => "Unable to seek DTB end", + Error::InvalidImage => "Invalid Image", + Error::InvalidImageMagicNumber => "Invalid Image magic number", + Error::DtbTooBig => "Device tree image too big", + Error::ReadKernelImage => "Unable to read kernel image", + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "PE Kernel Loader Error: {}", self.description()) + } +} + +#[repr(C)] +#[derive(Debug, Copy, Clone, Default)] +// See kernel doc Documentation/arm64/booting.txt for more information. +// All these fields should be little endian. +struct arm64_image_header { + code0: u32, + code1: u32, + text_offset: u64, + image_size: u64, + flags: u64, + res2: u64, + res3: u64, + res4: u64, + magic: u32, + res5: u32, +} + +impl KernelLoader for PE { + /// Loads a PE Image into guest memory. + /// + /// # 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 Image format kernel image. + /// * `highmem_start_address` - ignored on ARM64. + /// + /// # Returns + /// * KernelLoaderResult + fn load( + guest_mem: &M, + kernel_start: Option, + kernel_image: &mut F, + _highmem_start_address: Option, + ) -> Result + where + F: Read + Seek, + { + let kernel_size = kernel_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekImageEnd)? as usize; + let mut arm64_header: arm64_image_header = Default::default(); + kernel_image + .seek(SeekFrom::Start(0)) + .map_err(|_| Error::SeekImageHeader)?; + + arm64_header + .as_bytes() + .read_from(0, kernel_image, mem::size_of::()) + .map_err(|_| Error::ReadImageHeader)?; + + if u32::from_le(arm64_header.magic) != 0x644d_5241 { + return Err(Error::InvalidImageMagicNumber.into()); + } + + let image_size = u64::from_le(arm64_header.image_size); + let mut text_offset = u64::from_le(arm64_header.text_offset); + + if image_size == 0 { + text_offset = 0x80000; + } + + let mem_offset = kernel_start + .unwrap_or(GuestAddress(0)) + .checked_add(text_offset) + .ok_or(Error::InvalidImage)?; + + let mut loader_result: KernelLoaderResult = Default::default(); + loader_result.kernel_load = mem_offset; + + kernel_image + .seek(SeekFrom::Start(0)) + .map_err(|_| Error::SeekImageHeader)?; + guest_mem + .read_exact_from(mem_offset, kernel_image, kernel_size) + .map_err(|_| Error::ReadKernelImage)?; + + loader_result.kernel_end = mem_offset + .raw_value() + .checked_add(kernel_size as GuestUsize) + .ok_or(KernelLoaderError::MemoryOverflow)?; + + Ok(loader_result) + } +} + +/// Writes the device tree to the given memory slice. +/// +/// # Arguments +/// +/// * `guest_mem` - A u8 slice that will be partially overwritten by the device tree blob. +/// * `guest_addr` - The address in `guest_mem` at which to load the device tree blob. +/// * `dtb_image` - The device tree blob. +#[cfg(target_arch = "aarch64")] +pub fn load_dtb( + guest_mem: &M, + guest_addr: GuestAddress, + dtb_image: &mut F, +) -> Result<()> +where + F: Read + Seek, +{ + let dtb_size = dtb_image + .seek(SeekFrom::End(0)) + .map_err(|_| Error::SeekDtbEnd)? as usize; + if dtb_size > 0x200000 { + return Err(Error::DtbTooBig.into()); + } + dtb_image + .seek(SeekFrom::Start(0)) + .map_err(|_| Error::SeekDtbStart)?; + guest_mem + .read_exact_from(guest_addr, dtb_image, dtb_size) + .map_err(|_| Error::ReadDtbImage.into()) +} + +#[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_image_bin() -> Vec { + let mut v = Vec::new(); + v.extend_from_slice(include_bytes!("test_image.bin")); + v + } + + #[test] + fn load_image() { + let gm = create_guest_mem(); + let mut image = make_image_bin(); + let kernel_addr = GuestAddress(0x200000); + + let loader_result = + PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None).unwrap(); + assert_eq!(loader_result.kernel_load.raw_value(), 0x280000); + assert_eq!(loader_result.kernel_end, 0x281000); + + image[0x39] = 0x0; + let loader_result = PE::load(&gm, Some(kernel_addr), &mut Cursor::new(&image), None); + assert_eq!( + loader_result, + Err(KernelLoaderError::Pe(Error::InvalidImageMagicNumber)) + ); + } +} diff --git a/src/loader/aarch64/pe/test_image.bin b/src/loader/aarch64/pe/test_image.bin new file mode 100644 index 00000000..dc12b05d Binary files /dev/null and b/src/loader/aarch64/pe/test_image.bin differ diff --git a/src/loader/mod.rs b/src/loader/mod.rs index ba30eff5..dd662922 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -23,7 +23,9 @@ use std::ffi::CStr; use std::fmt::{self, Display}; use std::io::{Read, Seek}; -use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize}; +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] +use vm_memory::ByteValued; +use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize}; #[allow(dead_code)] #[allow(non_camel_case_types)] @@ -31,6 +33,7 @@ use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsiz #[allow(non_upper_case_globals)] #[allow(missing_docs)] #[cfg_attr(feature = "cargo-clippy", allow(clippy::all))] +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub mod bootparam; #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] @@ -54,6 +57,10 @@ pub enum Error { #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] Elf(elf::Error), + /// Failed to load PE image. + #[cfg(all(feature = "pe", target_arch = "aarch64"))] + Pe(pe::Error), + /// Failed writing command line to guest memory. CommandLineCopy, /// Command line overflowed guest memory. @@ -74,6 +81,8 @@ impl StdError for Error { Error::Bzimage(ref e) => e.description(), #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))] Error::Elf(ref e) => e.description(), + #[cfg(all(feature = "pe", target_arch = "aarch64"))] + Error::Pe(ref e) => e.description(), Error::CommandLineCopy => "Failed writing command line to guest memory", Error::CommandLineOverflow => "Command line overflowed guest memory", @@ -103,6 +112,13 @@ impl From for Error { } } +#[cfg(all(feature = "pe", target_arch = "aarch64"))] +impl From for Error { + fn from(err: pe::Error) -> Self { + Error::Pe(err) + } +} + /// Result of [`KernelLoader.load()`](trait.KernelLoader.html#tymethod.load). /// /// This specifies where the kernel is loading and passes additional @@ -117,6 +133,7 @@ pub struct KernelLoaderResult { pub kernel_end: GuestUsize, /// 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. + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] pub setup_header: Option, /// This field optionally holds the address of a PVH entry point, indicating that /// the kernel supports the PVH boot protocol as described in: @@ -138,6 +155,7 @@ pub trait KernelLoader { F: Read + Seek; } +#[cfg(any(target_arch = "x86", target_arch = "x86_64"))] unsafe impl ByteValued for bootparam::setup_header {} /// Writes the command line string to the given guest memory slice.