From f8a2be583462eb383384610f6ecea70eafc9abde Mon Sep 17 00:00:00 2001 From: Qiu Wenbo Date: Sat, 28 Mar 2020 16:01:21 +0800 Subject: [PATCH 1/5] loader: add support for ARM64 PE format Signed-off-by: Qiu Wenbo --- Cargo.toml | 3 +- README.md | 7 +- src/lib.rs | 10 +-- src/loader/aarch64/mod.rs | 3 + src/loader/aarch64/pe/mod.rs | 148 +++++++++++++++++++++++++++++++++++ src/loader/mod.rs | 13 +++ 6 files changed, 173 insertions(+), 11 deletions(-) create mode 100644 src/loader/aarch64/pe/mod.rs 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/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..4ac57681 --- /dev/null +++ b/src/loader/aarch64/pe/mod.rs @@ -0,0 +1,148 @@ +// 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 read kernel image. + ReadKernelImage, + /// Unable to read Image header. + ReadImageHeader, + /// 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::InvalidImage => "Invalid Image", + Error::InvalidImageMagicNumber => "Invalid Image magic number", + 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) + } +} diff --git a/src/loader/mod.rs b/src/loader/mod.rs index ba30eff5..a9342ab8 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -54,6 +54,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 +78,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 +109,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 From 91ed35b6dca8055f14b475f4e22ed4a4757418da Mon Sep 17 00:00:00 2001 From: Qiu Wenbo Date: Sat, 28 Mar 2020 16:05:05 +0800 Subject: [PATCH 2/5] loader: do not expose setup_header on ARM64 Signed-off-by: Qiu Wenbo --- src/loader/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/loader/mod.rs b/src/loader/mod.rs index a9342ab8..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"))] @@ -130,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: @@ -151,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. From f4deb7769b2fd1bd8cde770ace7d3b95d1f47de2 Mon Sep 17 00:00:00 2001 From: Qiu Wenbo Date: Sat, 28 Mar 2020 16:14:33 +0800 Subject: [PATCH 3/5] tests: add simple test case for ARM64 Image loader The test_image.bin is a cut of the first 4096 bytes of a pre-compiled linux kernel ARM64 Image. You can generate by: make Image head -c 4096 arch/arm64/boot/Image > test_image.bin Signed-off-by: Qiu Wenbo --- src/loader/aarch64/pe/mod.rs | 38 +++++++++++++++++++++++++++ src/loader/aarch64/pe/test_image.bin | Bin 0 -> 4096 bytes 2 files changed, 38 insertions(+) create mode 100644 src/loader/aarch64/pe/test_image.bin diff --git a/src/loader/aarch64/pe/mod.rs b/src/loader/aarch64/pe/mod.rs index 4ac57681..d155c679 100644 --- a/src/loader/aarch64/pe/mod.rs +++ b/src/loader/aarch64/pe/mod.rs @@ -146,3 +146,41 @@ impl KernelLoader for PE { 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_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 0000000000000000000000000000000000000000..dc12b05dfe2488ed6def5b16597178f799a8ce62 GIT binary patch literal 4096 zcmeZ`Vwm{9UQvXBfr9}I9$W@fTo4{k(lN+4#Q`WA;L5;|vWf{s-2w(SCT=Ds5r%|5 z28IHl0wDO~*T%pgfTRq>M%4z91lbQ&N5~B@)7W4P5@@}W)QS?2dXO7I_Q7Z*GZcWL zdMSw|i6AkMKMH_qy+k22k~#+lh6C~n%vVS8Xc&y9fzdQDng&MGz-Ss6O#`E8U^ESk Rrh(BkFq#HN)4*U&0|2N<8!`X@ literal 0 HcmV?d00001 From 299c7f6dc3747e184ce5b121ca35cb4e801c2fe4 Mon Sep 17 00:00:00 2001 From: Qiu Wenbo Date: Sat, 28 Mar 2020 16:36:09 +0800 Subject: [PATCH 4/5] loader: device tree loading support on ARM64 Signed-off-by: Qiu Wenbo --- src/loader/aarch64/pe/mod.rs | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/loader/aarch64/pe/mod.rs b/src/loader/aarch64/pe/mod.rs index d155c679..feae0bc8 100644 --- a/src/loader/aarch64/pe/mod.rs +++ b/src/loader/aarch64/pe/mod.rs @@ -32,10 +32,18 @@ pub enum Error { 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. @@ -48,8 +56,12 @@ impl error::Error for Error { 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", } } @@ -147,6 +159,36 @@ impl KernelLoader for PE { } } +/// 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::*; From d0cd686c972ab250fae125f9cd53462299fb6d7d Mon Sep 17 00:00:00 2001 From: Qiu Wenbo Date: Sat, 28 Mar 2020 16:43:09 +0800 Subject: [PATCH 5/5] tests: update test coverage on aarch64 Signed-off-by: Qiu Wenbo --- coverage_config_aarch64.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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": "" }