Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aarch64 Image format and device tree loading support for linux-loader #16

Merged
merged 5 commits into from
Apr 1, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -53,5 +54,5 @@ docker run -it \
--volume $(pwd):/linux-loader \
rustvmm/dev:v${container_version}
cd linux-loader/
cargo test
cargo test
```
2 changes: 1 addition & 1 deletion coverage_config_aarch64.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"coverage_score": 74.1,
"coverage_score": 75.7,
"exclude_path": "",
"crate_features": ""
}
10 changes: 3 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/loader/aarch64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
//! Traits and structs for loading `aarch64` kernels into guest memory.

#![cfg(target_arch = "aarch64")]

#[cfg(feature = "pe")]
pub mod pe;
228 changes: 228 additions & 0 deletions src/loader/aarch64/pe/mod.rs
Original file line number Diff line number Diff line change
@@ -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<F, M: GuestMemory>(
guest_mem: &M,
kernel_start: Option<GuestAddress>,
kernel_image: &mut F,
_highmem_start_address: Option<GuestAddress>,
) -> Result<KernelLoaderResult>
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::<arm64_image_header>())
.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<F, M: GuestMemory>(
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<u8> {
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))
);
}
}
Binary file added src/loader/aarch64/pe/test_image.bin
Binary file not shown.
20 changes: 19 additions & 1 deletion src/loader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ 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)]
#[allow(non_snake_case)]
#[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"))]
Expand All @@ -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.
Expand All @@ -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",
Expand Down Expand Up @@ -103,6 +112,13 @@ impl From<bzimage::Error> for Error {
}
}

#[cfg(all(feature = "pe", target_arch = "aarch64"))]
impl From<pe::Error> 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
Expand All @@ -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<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:
Expand All @@ -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.
Expand Down