extern crate vm_memory;
use std::fmt;
use std::io::{Read, Seek};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use vm_memory::ByteValued;
use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub use crate::loader_gen::bootparam;
pub use crate::cmdline::Cmdline;
#[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))]
pub mod pe;
#[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))]
pub use pe::*;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))]
pub mod elf;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))]
pub use elf::*;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))]
pub mod bzimage;
#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))]
pub use bzimage::*;
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
Bzimage(bzimage::Error),
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
Elf(elf::Error),
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
Pe(pe::Error),
InvalidCommandLine,
CommandLineCopy,
CommandLineOverflow,
InvalidKernelStartAddress,
MemoryOverflow,
}
pub type Result<T> = std::result::Result<T, Error>;
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Kernel Loader: ")?;
match self {
#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
Error::Bzimage(ref e) => write!(f, "failed to load bzImage kernel image: {e}"),
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
Error::Elf(ref e) => write!(f, "failed to load ELF kernel image: {e}"),
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
Error::Pe(ref e) => write!(f, "failed to load PE kernel image: {e}"),
Error::InvalidCommandLine => write!(f, "invalid command line provided"),
Error::CommandLineCopy => write!(f, "failed writing command line to guest memory"),
Error::CommandLineOverflow => write!(f, "command line overflowed guest memory"),
Error::InvalidKernelStartAddress => write!(f, "invalid kernel start address"),
Error::MemoryOverflow => write!(f, "memory to load kernel image is not enough"),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
Error::Bzimage(ref e) => Some(e),
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
Error::Elf(ref e) => Some(e),
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
Error::Pe(ref e) => Some(e),
Error::InvalidCommandLine => None,
Error::CommandLineCopy => None,
Error::CommandLineOverflow => None,
Error::InvalidKernelStartAddress => None,
Error::MemoryOverflow => None,
}
}
}
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
impl From<elf::Error> for Error {
fn from(err: elf::Error) -> Self {
Error::Elf(err)
}
}
#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
impl From<bzimage::Error> for Error {
fn from(err: bzimage::Error) -> Self {
Error::Bzimage(err)
}
}
#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
impl From<pe::Error> for Error {
fn from(err: pe::Error) -> Self {
Error::Pe(err)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct KernelLoaderResult {
pub kernel_load: GuestAddress,
pub kernel_end: GuestUsize,
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub setup_header: Option<bootparam::setup_header>,
#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
pub pvh_boot_cap: elf::PvhBootCapability,
}
pub trait KernelLoader {
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: Read + ReadVolatile + Seek;
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe impl ByteValued for bootparam::setup_header {}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe impl ByteValued for bootparam::boot_params {}
pub fn load_cmdline<M: GuestMemory>(
guest_mem: &M,
guest_addr: GuestAddress,
cmdline: &Cmdline,
) -> Result<()> {
let cmdline_string = cmdline
.as_cstring()
.map_err(|_| Error::InvalidCommandLine)?;
let cmdline_bytes = cmdline_string.as_bytes_with_nul();
let end = guest_addr
.checked_add((cmdline_bytes.len() - 1) as u64)
.ok_or(Error::CommandLineOverflow)?;
if end > guest_mem.last_addr() {
return Err(Error::CommandLineOverflow);
}
guest_mem
.write_slice(cmdline_bytes, guest_addr)
.map_err(|_| Error::CommandLineCopy)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
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()
}
#[test]
fn test_cmdline_overflow() {
let gm = create_guest_mem();
let mut cl = Cmdline::new(10).unwrap();
cl.insert_str("12345").unwrap();
let cmdline_address = GuestAddress(u64::MAX - 5);
assert_eq!(
Err(Error::CommandLineOverflow),
load_cmdline(&gm, cmdline_address, &cl)
);
let cmdline_address = GuestAddress(MEM_SIZE - 5);
assert_eq!(
Err(Error::CommandLineOverflow),
load_cmdline(&gm, cmdline_address, &cl)
);
let cmdline_address = GuestAddress(MEM_SIZE - 6);
assert!(load_cmdline(&gm, cmdline_address, &cl).is_ok());
}
#[test]
fn test_cmdline_write_end_regresion() {
let gm = create_guest_mem();
let mut cmdline_address = GuestAddress(45);
let sample_buf = &[1; 100];
gm.write(sample_buf, cmdline_address).unwrap();
let mut cl = Cmdline::new(10).unwrap();
load_cmdline(&gm, cmdline_address, &cl).unwrap();
let val: u8 = gm.read_obj(cmdline_address).unwrap();
assert_eq!(val, b'\0');
cl.insert_str("123").unwrap();
load_cmdline(&gm, cmdline_address, &cl).unwrap();
let val: u8 = gm.read_obj(cmdline_address).unwrap();
assert_eq!(val, b'1');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj(cmdline_address).unwrap();
assert_eq!(val, b'2');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj(cmdline_address).unwrap();
assert_eq!(val, b'3');
cmdline_address = cmdline_address.unchecked_add(1);
let val: u8 = gm.read_obj(cmdline_address).unwrap();
assert_eq!(val, b'\0');
}
}