#![cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
use std::fmt;
use std::io::{Seek, SeekFrom};
use vm_memory::{Address, ByteValued, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile};
use crate::loader::{
bootparam, Error as KernelLoaderError, KernelLoader, KernelLoaderResult, Result,
};
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidBzImage,
Overflow,
ReadBzImageHeader,
ReadBzImageCompressedKernel,
SeekBzImageEnd,
SeekBzImageHeader,
SeekBzImageCompressedKernel,
Underflow,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let desc = match self {
Error::InvalidBzImage => "Invalid bzImage",
Error::Overflow => "Overflow occurred during an arithmetic operation",
Error::ReadBzImageHeader => "Unable to read bzImage header",
Error::ReadBzImageCompressedKernel => "Unable to read bzImage compressed kernel",
Error::SeekBzImageEnd => "Unable to seek bzImage end",
Error::SeekBzImageHeader => "Unable to seek bzImage header",
Error::SeekBzImageCompressedKernel => "Unable to seek bzImage compressed kernel",
Error::Underflow => "Underflow occurred during an arithmetic operation",
};
write!(f, "Kernel Loader: {}", desc)
}
}
impl std::error::Error for Error {}
pub struct BzImage;
impl KernelLoader for BzImage {
fn load<F, M: GuestMemory>(
guest_mem: &M,
kernel_offset: Option<GuestAddress>,
kernel_image: &mut F,
highmem_start_address: Option<GuestAddress>,
) -> Result<KernelLoaderResult>
where
F: ReadVolatile + Seek,
{
let mut kernel_size = kernel_image
.seek(SeekFrom::End(0))
.map_err(|_| Error::SeekBzImageEnd)? as usize;
kernel_image
.seek(SeekFrom::Start(0x1F1))
.map_err(|_| Error::SeekBzImageHeader)?;
let mut boot_header = bootparam::setup_header::default();
kernel_image
.read_volatile(&mut boot_header.as_bytes())
.map_err(|_| Error::ReadBzImageHeader)?;
if boot_header.header != 0x5372_6448 {
return Err(Error::InvalidBzImage.into());
}
if (boot_header.version < 0x0200) || ((boot_header.loadflags & 0x1) == 0x0) {
return Err(Error::InvalidBzImage.into());
}
let mut setup_size = boot_header.setup_sects as usize;
if setup_size == 0 {
setup_size = 4;
}
setup_size = setup_size
.checked_add(1)
.and_then(|setup_size| setup_size.checked_mul(512))
.ok_or(Error::Overflow)?;
kernel_size = kernel_size
.checked_sub(setup_size)
.ok_or(Error::Underflow)?;
if (highmem_start_address.is_some())
&& (u64::from(boot_header.code32_start) < highmem_start_address.unwrap().raw_value())
{
return Err(KernelLoaderError::InvalidKernelStartAddress);
}
let mem_offset = match kernel_offset {
Some(start) => start,
None => GuestAddress(u64::from(boot_header.code32_start)),
};
boot_header.code32_start = mem_offset.raw_value() as u32;
let mut loader_result = KernelLoaderResult {
setup_header: Some(boot_header),
kernel_load: mem_offset,
..Default::default()
};
kernel_image
.seek(SeekFrom::Start(setup_size as u64))
.map_err(|_| Error::SeekBzImageCompressedKernel)?;
guest_mem
.read_exact_volatile_from(mem_offset, kernel_image, kernel_size)
.map_err(|_| Error::ReadBzImageCompressedKernel)?;
loader_result.kernel_end = mem_offset
.raw_value()
.checked_add(kernel_size as GuestUsize)
.ok_or(KernelLoaderError::MemoryOverflow)?;
Ok(loader_result)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::{Cursor, Read};
use std::process::Command;
use vm_memory::{Address, GuestAddress};
type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>;
const MEM_SIZE: u64 = 0x100_0000;
fn create_guest_mem() -> GuestMemoryMmap {
GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap()
}
fn download_resources() {
let command = "./.buildkite/download_resources.sh";
let status = Command::new(command).status().unwrap();
if !status.success() {
panic!("Cannot run build script");
}
}
fn make_bzimage() -> Vec<u8> {
download_resources();
let mut v = Vec::new();
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/src/loader/bzimage/bzimage");
let mut f = File::open(path).unwrap();
f.read_to_end(&mut v).unwrap();
v
}
#[allow(non_snake_case)]
#[test]
fn test_load_bzImage() {
let gm = create_guest_mem();
let image = make_bzimage();
let mut kernel_offset = GuestAddress(0x200000);
let mut highmem_start_address = GuestAddress(0x0);
let mut loader_result = BzImage::load(
&gm,
Some(kernel_offset),
&mut Cursor::new(&image),
Some(highmem_start_address),
)
.unwrap();
let setup_header = loader_result.setup_header.unwrap();
assert_eq!(loader_result.kernel_load.raw_value(), 0x200000);
assert_eq!(
unsafe { std::ptr::addr_of!(setup_header.header).read_unaligned() },
0x53726448
);
assert_eq!(
unsafe { std::ptr::addr_of!(setup_header.version).read_unaligned() },
0x20f
);
assert_eq!(loader_result.setup_header.unwrap().loadflags, 1);
assert_eq!(loader_result.kernel_end, 0x8b5e40);
loader_result = BzImage::load(
&gm,
None,
&mut Cursor::new(&image),
Some(highmem_start_address),
)
.unwrap();
let setup_header = loader_result.setup_header.unwrap();
assert_eq!(loader_result.kernel_load.raw_value(), 0x100000);
loader_result = BzImage::load(&gm, None, &mut Cursor::new(&image), None).unwrap();
assert_eq!(
0x53726448,
unsafe { std::ptr::addr_of!(setup_header.header).read_unaligned() }
);
assert_eq!(loader_result.kernel_load.raw_value(), 0x100000);
kernel_offset = GuestAddress(0x1000);
highmem_start_address = GuestAddress(0x200000);
assert_eq!(
Some(KernelLoaderError::InvalidKernelStartAddress),
BzImage::load(
&gm,
Some(kernel_offset),
&mut Cursor::new(&image),
Some(highmem_start_address),
)
.err()
);
}
#[test]
fn test_invalid_bzimage_underflow() {
use crate::loader::Error as LoaderError;
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/loader/bzimage/fuzz_invalid_bzimage.bin"
);
let gm = create_guest_mem();
let mut image = File::open(path).unwrap();
let kernel_offset = GuestAddress(0x200000);
let highmem_start_address = GuestAddress(0x0);
let loader_result = BzImage::load(
&gm,
Some(kernel_offset),
&mut image,
Some(highmem_start_address),
);
assert_eq!(
loader_result.unwrap_err(),
LoaderError::Bzimage(Error::Underflow)
);
}
}