From 050af40382a5619278fc08eef7dfbde5591abd76 Mon Sep 17 00:00:00 2001 From: Daniel Verkamp Date: Mon, 13 May 2019 12:34:54 -0700 Subject: [PATCH] x86_64: support loading bzImage kernels The current kernel loader expects an extracted ELF kernel; this adds complexity to the build and test process for the guest kernel, since the normal output of a Linux kernel build is a bzImage-format kernel. bzImage also supports compressed kernels, which are smaller on disk and potentially quicker to load, depending on disk and CPU speed. Add support for loading of bzImage-format kernels, and use the 64-bit boot protocol as described in the official Linux/x86 boot protocol: https://www.kernel.org/doc/Documentation/x86/boot.txt The existing ELF loader is kept for compatibility with shipping kernel images; if a kernel image doesn't have the ELF signature, it is passed to the bzImage loader as a fallback. BUG=None TEST=Boot bzImage and extracted ELF kernels on x86-64 Change-Id: I90be4cd597d15bc89e63f0f6cbc781c5c8c2eaeb Signed-off-by: Daniel Verkamp Reviewed-on: https://chromium-review.googlesource.com/1609969 Tested-by: kokoro Reviewed-by: Dylan Reid Reviewed-by: Zach Reizner --- x86_64/src/bzimage.rs | 101 ++++++++++++++++++++++++++++++++++++++++++ x86_64/src/lib.rs | 24 +++++++--- 2 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 x86_64/src/bzimage.rs diff --git a/x86_64/src/bzimage.rs b/x86_64/src/bzimage.rs new file mode 100644 index 0000000000..16a7338932 --- /dev/null +++ b/x86_64/src/bzimage.rs @@ -0,0 +1,101 @@ +// Copyright 2019 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 file. + +// Loader for bzImage-format Linux kernels as described in +// https://www.kernel.org/doc/Documentation/x86/boot.txt + +use std::fmt::{self, Display}; +use std::io::{Read, Seek, SeekFrom}; + +use sys_util::{GuestAddress, GuestMemory}; + +use crate::bootparam::boot_params; + +#[derive(Debug, PartialEq)] +pub enum Error { + BadSignature, + InvalidSetupSects, + InvalidSysSize, + ReadBootParams, + ReadKernelImage, + SeekBootParams, + SeekKernelStart, +} +pub type Result = std::result::Result; + +impl std::error::Error for Error {} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::Error::*; + + let description = match self { + BadSignature => "bad kernel header signature", + InvalidSetupSects => "invalid setup_sects value", + InvalidSysSize => "invalid syssize value", + ReadBootParams => "unable to read boot_params", + ReadKernelImage => "unable to read kernel image", + SeekBootParams => "unable to seek to boot_params", + SeekKernelStart => "unable to seek to kernel start", + }; + + write!(f, "bzImage loader: {}", description) + } +} + +/// Loads a kernel from a bzImage to a slice +/// +/// # 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 bzImage. +pub fn load_bzimage( + guest_mem: &GuestMemory, + kernel_start: GuestAddress, + kernel_image: &mut F, +) -> Result<(boot_params, u64)> +where + F: Read + Seek, +{ + let mut params: boot_params = Default::default(); + kernel_image + .seek(SeekFrom::Start(0)) + .map_err(|_| Error::SeekBootParams)?; + unsafe { + // read_struct is safe when reading a POD struct. It can be used and dropped without issue. + sys_util::read_struct(kernel_image, &mut params).map_err(|_| Error::ReadBootParams)?; + } + + // bzImage header signature "HdrS" + if params.hdr.header != 0x53726448 { + return Err(Error::BadSignature); + } + + let setup_sects = if params.hdr.setup_sects == 0 { + 4u64 + } else { + params.hdr.setup_sects as u64 + }; + + let kernel_offset = setup_sects + .checked_add(1) + .ok_or(Error::InvalidSetupSects)? + .checked_mul(512) + .ok_or(Error::InvalidSetupSects)?; + let kernel_size = (params.hdr.syssize as usize) + .checked_mul(16) + .ok_or(Error::InvalidSysSize)?; + + kernel_image + .seek(SeekFrom::Start(kernel_offset)) + .map_err(|_| Error::SeekKernelStart)?; + + // Load the whole kernel image to kernel_start + guest_mem + .read_to_memory(kernel_start, kernel_image, kernel_size) + .map_err(|_| Error::ReadKernelImage)?; + + Ok((params, kernel_start.offset() + kernel_size as u64)) +} diff --git a/x86_64/src/lib.rs b/x86_64/src/lib.rs index 455e0eaf8e..23df5ea578 100644 --- a/x86_64/src/lib.rs +++ b/x86_64/src/lib.rs @@ -46,6 +46,7 @@ unsafe impl data_model::DataInit for mpspec::mpc_table {} unsafe impl data_model::DataInit for mpspec::mpc_lintsrc {} unsafe impl data_model::DataInit for mpspec::mpf_intel {} +mod bzimage; mod cpuid; mod gdt; mod interrupts; @@ -91,6 +92,7 @@ pub enum Error { CreateVm(sys_util::Error), E820Configuration, KernelOffsetPastEnd, + LoadBzImage(bzimage::Error), LoadCmdline(kernel_loader::Error), LoadInitrd(arch::LoadImageError), LoadKernel(kernel_loader::Error), @@ -133,6 +135,7 @@ impl Display for Error { CreateVm(e) => write!(f, "failed to create VM: {}", e), E820Configuration => write!(f, "invalid e820 setup params"), KernelOffsetPastEnd => write!(f, "the kernel extends past the end of RAM"), + LoadBzImage(e) => write!(f, "error loading kernel bzImage: {}", e), LoadCmdline(e) => write!(f, "error loading command line: {}", e), LoadInitrd(e) => write!(f, "error loading initrd: {}", e), LoadKernel(e) => write!(f, "error loading Kernel: {}", e), @@ -181,6 +184,7 @@ fn configure_system( pci_irqs: Vec<(u32, PciInterruptPin)>, setup_data: Option, initrd: Option<(GuestAddress, usize)>, + mut params: boot_params, ) -> Result<()> { const EBDA_START: u64 = 0x0009fc00; const KERNEL_BOOT_FLAG_MAGIC: u16 = 0xaa55; @@ -195,8 +199,6 @@ fn configure_system( smbios::setup_smbios(guest_mem).map_err(Error::SetupSmbios)?; - let mut params: boot_params = Default::default(); - params.hdr.type_of_loader = KERNEL_LOADER_OTHER; params.hdr.boot_flag = KERNEL_BOOT_FLAG_MAGIC; params.hdr.header = KERNEL_HDR_MAGIC; @@ -353,7 +355,7 @@ impl arch::LinuxArch for X8664arch { // separate out load_kernel from other setup to get a specific error for // kernel loading - let kernel_end = Self::load_kernel(&mem, &mut components.kernel_image)?; + let (params, kernel_end) = Self::load_kernel(&mem, &mut components.kernel_image)?; Self::setup_system_memory( &mem, @@ -364,6 +366,7 @@ impl arch::LinuxArch for X8664arch { pci_irqs, components.android_fstab, kernel_end, + params, )?; Ok(RunnableLinuxVm { @@ -389,9 +392,16 @@ impl X8664arch { /// /// * `mem` - The memory to be used by the guest. /// * `kernel_image` - the File object for the specified kernel. - fn load_kernel(mem: &GuestMemory, mut kernel_image: &mut File) -> Result { - kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image) - .map_err(Error::LoadKernel) + fn load_kernel(mem: &GuestMemory, mut kernel_image: &mut File) -> Result<(boot_params, u64)> { + let elf_result = + kernel_loader::load_kernel(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image); + if elf_result == Err(kernel_loader::Error::InvalidElfMagicNumber) { + bzimage::load_bzimage(mem, GuestAddress(KERNEL_START_OFFSET), &mut kernel_image) + .map_err(Error::LoadBzImage) + } else { + let kernel_end = elf_result.map_err(Error::LoadKernel)?; + Ok((Default::default(), kernel_end)) + } } /// Configures the system memory space should be called once per vm before @@ -412,6 +422,7 @@ impl X8664arch { pci_irqs: Vec<(u32, PciInterruptPin)>, android_fstab: Option, kernel_end: u64, + params: boot_params, ) -> Result<()> { kernel_loader::load_cmdline(mem, GuestAddress(CMDLINE_OFFSET), cmdline) .map_err(Error::LoadCmdline)?; @@ -462,6 +473,7 @@ impl X8664arch { pci_irqs, setup_data, initrd, + params, )?; Ok(()) }