From ead6b8fbab76acbdc374c673885a43f3c82d6f08 Mon Sep 17 00:00:00 2001 From: Alexandra Iordache Date: Thu, 26 Mar 2020 20:18:47 +0200 Subject: [PATCH] write bootparams in memory for Linux & PVH protos This commit introduces a new configurator module that takes boot parameters created in the VMM (boot_params / start_info + e820 map) and writes them into guest memory. The module is meant to be extended in order to include *building* the boot parameters as well. Fixes #15 Signed-off-by: Alexandra Iordache --- src/configurator/aarch64/mod.rs | 7 + src/configurator/mod.rs | 27 ++++ src/configurator/x86_64/linux.rs | 146 +++++++++++++++++++ src/configurator/x86_64/mod.rs | 112 +++++++++++++++ src/configurator/x86_64/pvh.rs | 231 +++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/loader/mod.rs | 1 + 7 files changed, 525 insertions(+) create mode 100644 src/configurator/aarch64/mod.rs create mode 100644 src/configurator/mod.rs create mode 100644 src/configurator/x86_64/linux.rs create mode 100644 src/configurator/x86_64/mod.rs create mode 100644 src/configurator/x86_64/pvh.rs diff --git a/src/configurator/aarch64/mod.rs b/src/configurator/aarch64/mod.rs new file mode 100644 index 00000000..88ca9d2e --- /dev/null +++ b/src/configurator/aarch64/mod.rs @@ -0,0 +1,7 @@ +// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause + +//! Traits and structs for configuring and loading boot parameters on `aarch64`. + +#![cfg(target_arch = "aarch64")] diff --git a/src/configurator/mod.rs b/src/configurator/mod.rs new file mode 100644 index 00000000..3ad71ec1 --- /dev/null +++ b/src/configurator/mod.rs @@ -0,0 +1,27 @@ +// Copyright © 2020, Oracle and/or its affiliates. +// +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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 configuring and loading boot parameters. +//! - [BootConfigurator](trait.BootConfigurator.html): configure boot parameters. +//! - [LinuxBootConfigurator](struct.LinuxBootConfigurator.html): Linux boot protocol parameters +//! configurator. +//! - [PvhBootConfigurator](struct.PvhBootConfigurator.html): PVH boot protocol parameters +//! configurator. + +#[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(target_arch = "aarch64")] +mod aarch64; +#[cfg(target_arch = "aarch64")] +pub use aarch64::*; diff --git a/src/configurator/x86_64/linux.rs b/src/configurator/x86_64/linux.rs new file mode 100644 index 00000000..97961c39 --- /dev/null +++ b/src/configurator/x86_64/linux.rs @@ -0,0 +1,146 @@ +// Copyright © 2020, Oracle and/or its affiliates. +// +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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 configuring and loading boot parameters on `x86_64` using the Linux +//! boot protocol. + +use vm_memory::{ByteValued, Bytes, GuestAddress, GuestMemory}; + +use super::super::{BootConfigurator, Result}; +use crate::loader::bootparam::boot_params; + +use std::error::Error as StdError; +use std::fmt; +use std::mem; + +/// Boot configurator for the Linux boot protocol. +pub struct LinuxBootConfigurator {} + +/// Errors specific to the Linux boot protocol configuration. +#[derive(Debug, PartialEq)] +pub enum Error { + /// The zero page extends past the end of guest memory. + ZeroPagePastRamEnd, + /// Error writing to the zero page of guest memory. + ZeroPageSetup, +} + +impl StdError for Error { + fn description(&self) -> &str { + use Error::*; + match self { + ZeroPagePastRamEnd => "The zero page extends past the end of guest memory.", + ZeroPageSetup => "Error writing to the zero page of guest memory.", + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Linux Boot Configurator Error: {}", + StdError::description(self) + ) + } +} + +impl BootConfigurator for LinuxBootConfigurator { + /// Writes the boot parameters (configured elsewhere) into guest memory. + /// + /// # Arguments + /// + /// * `header` - boot parameters encapsulated in a [`boot_params`](struct.boot_params.html) + /// struct. + /// * `sections` - unused. + /// * `guest_memory` - guest's physical memory. + fn write_bootparams( + header: (T, GuestAddress), + _sections: Option<(Vec, GuestAddress)>, + guest_memory: &M, + ) -> Result<()> + where + T: ByteValued, + S: ByteValued, + M: GuestMemory, + { + // The VMM has filled a `boot_params` struct and its e820 map. + // This will be written in guest memory at the zero page. + guest_memory + .checked_offset(header.1, mem::size_of::()) + .ok_or(Error::ZeroPagePastRamEnd)?; + guest_memory + .write_obj(header.0, header.1) + .map_err(|_| Error::ZeroPageSetup)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::mem; + use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + + const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; + const KERNEL_HDR_MAGIC: u32 = 0x53726448; + const KERNEL_LOADER_OTHER: u8 = 0xff; + const KERNEL_MIN_ALIGNMENT_BYTES: u32 = 0x1000000; + const MEM_SIZE: u64 = 0x1000000; + + fn create_guest_mem() -> GuestMemoryMmap { + GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() + } + + fn build_bootparams_common() -> boot_params { + let mut params = boot_params::default(); + params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC; + params.hdr.header = KERNEL_HDR_MAGIC; + params.hdr.kernel_alignment = KERNEL_MIN_ALIGNMENT_BYTES; + params.hdr.type_of_loader = KERNEL_LOADER_OTHER; + params + } + + #[test] + fn test_configure_linux_boot() { + let zero_page_addr = GuestAddress(0x30000); + + let params = build_bootparams_common(); + // This is where we'd append e820 entries, cmdline, PCI, ACPI etc. + + let guest_memory = create_guest_mem(); + + // Error case: boot params don't fit in guest memory (zero page address too close to end). + let bad_zeropg_addr = GuestAddress( + guest_memory.last_addr().raw_value() - mem::size_of::() as u64 + 1, + ); + assert_eq!( + LinuxBootConfigurator::write_bootparams::( + (params, bad_zeropg_addr), + None, + &guest_memory, + ) + .err(), + Some(Error::ZeroPagePastRamEnd.into()), + ); + + // Success case. + assert!( + LinuxBootConfigurator::write_bootparams::( + (params, zero_page_addr), + None, + &guest_memory + ) + .is_ok() + ); + } +} diff --git a/src/configurator/x86_64/mod.rs b/src/configurator/x86_64/mod.rs new file mode 100644 index 00000000..bc9aff7c --- /dev/null +++ b/src/configurator/x86_64/mod.rs @@ -0,0 +1,112 @@ +// Copyright © 2020, Oracle and/or its affiliates. +// +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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 configuring and loading boot parameters on `x86_64`. + +#![cfg(any(target_arch = "x86", target_arch = "x86_64"))] + +extern crate vm_memory; + +use vm_memory::{ByteValued, GuestAddress, GuestMemory}; + +use std::error::Error as StdError; +use std::fmt; + +pub mod linux; +pub mod pvh; + +/// Errors specific to boot protocol configuration. +#[derive(Debug, PartialEq)] +pub enum Error { + /// Errors specific to the Linux boot protocol configuration. + Linux(linux::Error), + /// Errors specific to the PVH boot protocol configuration. + Pvh(pvh::Error), +} + +impl StdError for Error { + fn description(&self) -> &str { + use Error::*; + match self { + Linux(ref e) => e.description(), + Pvh(ref e) => e.description(), + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "Boot Configurator Error: {}", + StdError::description(self) + ) + } +} + +impl From for Error { + fn from(err: linux::Error) -> Self { + Error::Linux(err) + } +} + +impl From for Error { + fn from(err: pvh::Error) -> Self { + Error::Pvh(err) + } +} + +/// A specialized [`Result`] type for the boot configurator. +/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html +pub type Result = std::result::Result; + +/// Trait that defines interfaces for building (TBD) and configuring boot parameters. +/// +/// Currently, this trait exposes a single function which writes user-provided boot parameters into +/// guest memory at the user-specified addresses. It's meant to be called after the kernel is +/// loaded and after the boot parameters are built externally (in the VMM). +/// +/// This trait will be extended with additional functionality to build boot parameters. +pub trait BootConfigurator { + /// Writes the boot parameters (configured elsewhere) into guest memory. + /// + /// The arguments are split into `header` and `sections` to accommodate different boot + /// protocols like Linux boot and PVH. In Linux boot, the e820 map could be considered as + /// `sections`, but it's already encapsulated in the `boot_params` and thus all the boot + /// parameters are passed through a single struct. In PVH, the memory map table is separated + /// from the `hvm_start_info` struct, therefore it's passed separately. + /// + /// # Arguments + /// + /// * `header` - header section of the boot parameters and address where to write it in guest + /// memory. The first element must be a POD struct that implements [`ByteValued`]. + /// For the Linux protocol it's the [`boot_params`] struct, and for PVH the + /// [`hvm_start_info`] struct. + /// * `sections` - vector of sections that compose the boot parameters and address where to + /// write them in guest memory. Unused for the Linux protocol. For PVH, it's the + /// memory map table represented as a vector of [`hvm_memmap_table_entry`]. Must + /// be a `Vec` of POD data structs that implement [`ByteValued`]. + /// * `guest_memory` - guest's physical memory. + /// + /// [`boot_params`]: struct.boot_params.html + /// [`hvm_memmap_table_entry`]: struct.hvm_memmap_table_entry.html + /// [`hvm_start_info`]: struct.hvm_start_info.html + /// [`ByteValued`]: https://docs.rs/vm-memory/latest/vm_memory/bytes/trait.ByteValued.html + fn write_bootparams( + header: (T, GuestAddress), + sections: Option<(Vec, GuestAddress)>, + guest_memory: &M, + ) -> Result<()> + where + T: ByteValued, + S: ByteValued, + M: GuestMemory; +} diff --git a/src/configurator/x86_64/pvh.rs b/src/configurator/x86_64/pvh.rs new file mode 100644 index 00000000..99dd0a37 --- /dev/null +++ b/src/configurator/x86_64/pvh.rs @@ -0,0 +1,231 @@ +// Copyright © 2020, Oracle and/or its affiliates. +// +// Copyright (c) 2019 Intel Corporation. All rights reserved. +// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. +// +// 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 configuring and loading boot parameters on `x86_64` using the PVH boot +//! protocol. + +use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory}; + +use super::super::{BootConfigurator, Result}; +use crate::loader::elf::start_info::{hvm_memmap_table_entry, hvm_start_info}; + +use std::error::Error as StdError; +use std::fmt; +use std::mem; + +/// Boot configurator for the PVH boot protocol. +pub struct PvhBootConfigurator {} + +/// Errors specific to the PVH boot protocol configuration. +#[derive(Debug, PartialEq)] +pub enum Error { + /// No memory map wasn't passed to the boot configurator. + MemmapTableMissing, + /// The memory map table extends past the end of guest memory. + MemmapTablePastRamEnd, + /// Error writing memory map table to guest memory. + MemmapTableSetup, + /// The hvm_start_info structure extends past the end of guest memory. + StartInfoPastRamEnd, + /// Error writing hvm_start_info to guest memory. + StartInfoSetup, +} + +impl StdError for Error { + fn description(&self) -> &str { + use Error::*; + match self { + MemmapTableMissing => "No memory map wasn't passed to the boot configurator.", + MemmapTablePastRamEnd => "The memory map table extends past the end of guest memory.", + MemmapTableSetup => "Error writing memory map table to guest memory.", + StartInfoPastRamEnd => { + "The hvm_start_info structure extends past the end of guest memory." + } + StartInfoSetup => "Error writing hvm_start_info to guest memory.", + } + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "PVH Boot Configurator Error: {}", + StdError::description(self) + ) + } +} + +unsafe impl ByteValued for hvm_start_info {} +unsafe impl ByteValued for hvm_memmap_table_entry {} + +impl BootConfigurator for PvhBootConfigurator { + /// Writes the boot parameters (configured elsewhere) into guest memory. + /// + /// # Arguments + /// + /// * `header` - boot parameters encapsulated in a + /// [`hvm_start_info`](struct.hvm_start_info.html) struct. + /// * `sections` - memory map table represented as a vector of + /// [`hvm_memmap_table_entry`](struct.hvm_memmap_table_entry.html). + /// * `guest_memory` - guest's physical memory. + fn write_bootparams( + header: (T, GuestAddress), + sections: Option<(Vec, GuestAddress)>, + guest_memory: &M, + ) -> Result<()> + where + T: ByteValued, + S: ByteValued, + M: GuestMemory, + { + // The VMM has filled an `hvm_start_info` struct and a `Vec` + // and has passed them on to this function. + // The `hvm_start_info` will be written at `addr` and the memmap entries at + // `start_info.0.memmap_paddr`. + let (memmap_entries, mut memmap_start_addr) = sections.ok_or(Error::MemmapTableMissing)?; + guest_memory + .checked_offset( + memmap_start_addr, + mem::size_of::() * memmap_entries.len(), + ) + .ok_or(Error::MemmapTablePastRamEnd)?; + + for memmap_entry in memmap_entries { + guest_memory + .write_obj(memmap_entry, memmap_start_addr) + .map_err(|_| Error::MemmapTableSetup)?; + memmap_start_addr = + memmap_start_addr.unchecked_add(mem::size_of::() as u64); + } + + guest_memory + .checked_offset(header.1, mem::size_of::()) + .ok_or(Error::StartInfoPastRamEnd)?; + guest_memory + .write_obj(header.0, header.1) + .map_err(|_| Error::StartInfoSetup)?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use vm_memory::{Address, GuestAddress, GuestMemoryMmap}; + + const XEN_HVM_START_MAGIC_VALUE: u32 = 0x336ec578; + const MEM_SIZE: u64 = 0x1000000; + const E820_RAM: u32 = 1; + + fn create_guest_mem() -> GuestMemoryMmap { + GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap() + } + + fn add_memmap_entry( + memmap_entries: &mut Vec, + addr: GuestAddress, + size: u64, + mem_type: u32, + ) { + // Add the table entry to the vector + memmap_entries.push(hvm_memmap_table_entry { + addr: addr.raw_value(), + size, + type_: mem_type, + reserved: 0, + }); + } + + fn build_bootparams_common() -> (hvm_start_info, Vec) { + let mut start_info = hvm_start_info::default(); + let memmap_entries: Vec = vec![]; + + start_info.magic = XEN_HVM_START_MAGIC_VALUE; + start_info.version = 1; + start_info.nr_modules = 0; + start_info.memmap_entries = 0; + + (start_info, memmap_entries) + } + + #[test] + fn test_configure_pvh_boot() { + let (mut start_info, mut memmap_entries) = build_bootparams_common(); + let guest_memory = create_guest_mem(); + + let start_info_addr = GuestAddress(0x6000); + let memmap_addr = GuestAddress(0x7000); + start_info.memmap_paddr = memmap_addr.raw_value(); + + // Error case: configure without memory map. + assert_eq!( + PvhBootConfigurator::write_bootparams::< + hvm_start_info, + hvm_memmap_table_entry, + GuestMemoryMmap, + >((start_info, start_info_addr), None, &guest_memory,) + .err(), + Some(Error::MemmapTableMissing.into()) + ); + + // Error case: start_info doesn't fit in guest memory. + let bad_start_info_addr = GuestAddress( + guest_memory.last_addr().raw_value() - mem::size_of::() as u64 + 1, + ); + assert_eq!( + PvhBootConfigurator::write_bootparams::< + hvm_start_info, + hvm_memmap_table_entry, + GuestMemoryMmap, + >( + (start_info, bad_start_info_addr), + Some((memmap_entries.clone(), memmap_addr)), + &guest_memory, + ) + .err(), + Some(Error::StartInfoPastRamEnd.into()) + ); + + // Error case: memory map doesn't fit in guest memory. + let himem_start = GuestAddress(0x100000); + add_memmap_entry(&mut memmap_entries, himem_start, 0, E820_RAM); + let bad_memmap_addr = GuestAddress( + guest_memory.last_addr().raw_value() - mem::size_of::() as u64 + + 1, + ); + assert_eq!( + PvhBootConfigurator::write_bootparams::< + hvm_start_info, + hvm_memmap_table_entry, + GuestMemoryMmap, + >( + (start_info, start_info_addr), + Some((memmap_entries.clone(), bad_memmap_addr)), + &guest_memory, + ) + .err(), + Some(Error::MemmapTablePastRamEnd.into()) + ); + + assert!(PvhBootConfigurator::write_bootparams::< + hvm_start_info, + hvm_memmap_table_entry, + GuestMemoryMmap, + >( + (start_info, start_info_addr), + Some((memmap_entries.clone(), memmap_addr)), + &guest_memory, + ) + .is_ok()); + } +} diff --git a/src/lib.rs b/src/lib.rs index fadbd490..16cac6d5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,6 +27,7 @@ //! will make it consumable by other platforms. pub mod cmdline; +pub mod configurator; pub mod loader; extern crate vm_memory; diff --git a/src/loader/mod.rs b/src/loader/mod.rs index ba30eff5..15a038e3 100644 --- a/src/loader/mod.rs +++ b/src/loader/mod.rs @@ -139,6 +139,7 @@ pub trait KernelLoader { } unsafe impl ByteValued for bootparam::setup_header {} +unsafe impl ByteValued for bootparam::boot_params {} /// Writes the command line string to the given guest memory slice. ///