mirror of
https://chromium.googlesource.com/crosvm/crosvm
synced 2025-02-09 20:04:20 +00:00
kernel_loader: add arm64 kernel loader
The new load_arm64_kernel() function loads and validates the Linux arm64 image format. This allows detecting whether the magic number and other fields are valid, similar to the existing ELF loader. BUG=b:254601048 TEST=Boot Crostini on arm Change-Id: I62dc4670c2a216aa9a062f6d57ff945162e26037 Reviewed-on: https://chromium-review.googlesource.com/c/crosvm/crosvm/+/3976062 Commit-Queue: Daniel Verkamp <dverkamp@chromium.org> Reviewed-by: Alexandre Courbot <acourbot@chromium.org>
This commit is contained in:
parent
23e3de8afb
commit
b6c3227d2d
3 changed files with 252 additions and 34 deletions
|
@ -51,6 +51,7 @@ use hypervisor::VcpuInitAArch64;
|
|||
use hypervisor::VcpuRegAArch64;
|
||||
use hypervisor::Vm;
|
||||
use hypervisor::VmAArch64;
|
||||
use kernel_loader::LoadedKernel;
|
||||
use minijail::Minijail;
|
||||
use remain::sorted;
|
||||
use resources::AddressRange;
|
||||
|
@ -108,6 +109,14 @@ const PSR_I_BIT: u64 = 0x00000080;
|
|||
const PSR_A_BIT: u64 = 0x00000100;
|
||||
const PSR_D_BIT: u64 = 0x00000200;
|
||||
|
||||
enum PayloadType {
|
||||
Bios {
|
||||
entry: GuestAddress,
|
||||
image_size: u64,
|
||||
},
|
||||
Kernel(LoadedKernel),
|
||||
}
|
||||
|
||||
fn get_kernel_addr() -> GuestAddress {
|
||||
GuestAddress(AARCH64_PHYS_MEM_START + AARCH64_KERNEL_OFFSET)
|
||||
}
|
||||
|
@ -204,7 +213,7 @@ pub enum Error {
|
|||
#[error("initrd could not be loaded: {0}")]
|
||||
InitrdLoadFailure(arch::LoadImageError),
|
||||
#[error("kernel could not be loaded: {0}")]
|
||||
KernelLoadFailure(arch::LoadImageError),
|
||||
KernelLoadFailure(kernel_loader::Error),
|
||||
#[error("error loading Kernel from Elf image: {0}")]
|
||||
LoadElfKernel(kernel_loader::Error),
|
||||
#[error("failed to map arm pvtime memory: {0}")]
|
||||
|
@ -323,25 +332,26 @@ impl arch::LinuxArch for AArch64 {
|
|||
// separate out image loading from other setup to get a specific error for
|
||||
// image loading
|
||||
let mut initrd = None;
|
||||
let image_size = match components.vm_image {
|
||||
let payload = match components.vm_image {
|
||||
VmImage::Bios(ref mut bios) => {
|
||||
arch::load_image(&mem, bios, get_bios_addr(), AARCH64_BIOS_MAX_LEN)
|
||||
.map_err(Error::BiosLoadFailure)?
|
||||
let image_size =
|
||||
arch::load_image(&mem, bios, get_bios_addr(), AARCH64_BIOS_MAX_LEN)
|
||||
.map_err(Error::BiosLoadFailure)?;
|
||||
PayloadType::Bios {
|
||||
entry: get_bios_addr(),
|
||||
image_size: image_size as u64,
|
||||
}
|
||||
}
|
||||
VmImage::Kernel(ref mut kernel_image) => {
|
||||
let kernel_end: u64;
|
||||
let kernel_size: usize;
|
||||
let elf_result = kernel_loader::load_elf64(&mem, get_kernel_addr(), kernel_image);
|
||||
if elf_result == Err(kernel_loader::Error::InvalidMagicNumber) {
|
||||
kernel_size =
|
||||
arch::load_image(&mem, kernel_image, get_kernel_addr(), u64::max_value())
|
||||
.map_err(Error::KernelLoadFailure)?;
|
||||
kernel_end = get_kernel_addr().offset() + kernel_size as u64;
|
||||
let loaded_kernel = if let Ok(elf_kernel) =
|
||||
kernel_loader::load_elf64(&mem, get_kernel_addr(), kernel_image)
|
||||
{
|
||||
elf_kernel
|
||||
} else {
|
||||
let loaded_kernel = elf_result.map_err(Error::LoadElfKernel)?;
|
||||
kernel_size = loaded_kernel.size as usize;
|
||||
kernel_end = loaded_kernel.address_range.end;
|
||||
}
|
||||
kernel_loader::load_arm64_kernel(&mem, get_kernel_addr(), kernel_image)
|
||||
.map_err(Error::KernelLoadFailure)?
|
||||
};
|
||||
let kernel_end = loaded_kernel.address_range.end;
|
||||
initrd = match components.initrd_image {
|
||||
Some(initrd_file) => {
|
||||
let mut initrd_file = initrd_file;
|
||||
|
@ -357,7 +367,7 @@ impl arch::LinuxArch for AArch64 {
|
|||
}
|
||||
None => None,
|
||||
};
|
||||
kernel_size
|
||||
PayloadType::Kernel(loaded_kernel)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -379,9 +389,8 @@ impl arch::LinuxArch for AArch64 {
|
|||
.map_err(|_| Error::DowncastVcpu)?;
|
||||
let per_vcpu_init = Self::vcpu_init(
|
||||
vcpu_id,
|
||||
has_bios,
|
||||
&payload,
|
||||
fdt_offset,
|
||||
image_size,
|
||||
components.hv_cfg.protection_type,
|
||||
);
|
||||
has_pvtime &= vcpu.has_pvtime_support();
|
||||
|
@ -843,9 +852,8 @@ impl AArch64 {
|
|||
/// * `vcpu_id` - The VM's index for `vcpu`.
|
||||
fn vcpu_init(
|
||||
vcpu_id: usize,
|
||||
has_bios: bool,
|
||||
payload: &PayloadType,
|
||||
fdt_address: GuestAddress,
|
||||
image_size: usize,
|
||||
protection_type: ProtectionType,
|
||||
) -> VcpuInitAArch64 {
|
||||
let mut regs: BTreeMap<VcpuRegAArch64, u64> = Default::default();
|
||||
|
@ -856,10 +864,9 @@ impl AArch64 {
|
|||
|
||||
// Other cpus are powered off initially
|
||||
if vcpu_id == 0 {
|
||||
let image_addr = if has_bios {
|
||||
get_bios_addr()
|
||||
} else {
|
||||
get_kernel_addr()
|
||||
let (image_addr, image_size) = match payload {
|
||||
PayloadType::Bios { entry, image_size } => (*entry, *image_size),
|
||||
PayloadType::Kernel(loaded_kernel) => (loaded_kernel.entry, loaded_kernel.size),
|
||||
};
|
||||
|
||||
let entry_addr = if protection_type.loads_firmware() {
|
||||
|
@ -922,12 +929,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn vcpu_init_unprotected_kernel() {
|
||||
let has_bios = false;
|
||||
let payload = PayloadType::Kernel(LoadedKernel {
|
||||
address_range: AddressRange::from_start_and_size(0x8080_0000, 0x1000).unwrap(),
|
||||
size: 0x1000,
|
||||
entry: GuestAddress(0x8080_0000),
|
||||
});
|
||||
let fdt_address = GuestAddress(0x1234);
|
||||
let image_size = 0x1000;
|
||||
let prot = ProtectionType::Unprotected;
|
||||
|
||||
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
|
||||
let vcpu_init = AArch64::vcpu_init(0, &payload, fdt_address, prot);
|
||||
|
||||
// PC: kernel image entry point
|
||||
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::Pc), Some(&0x8080_0000));
|
||||
|
@ -938,12 +948,14 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn vcpu_init_unprotected_bios() {
|
||||
let has_bios = true;
|
||||
let payload = PayloadType::Bios {
|
||||
entry: GuestAddress(0x8020_0000),
|
||||
image_size: 0x1000,
|
||||
};
|
||||
let fdt_address = GuestAddress(0x1234);
|
||||
let image_size = 0x1000;
|
||||
let prot = ProtectionType::Unprotected;
|
||||
|
||||
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
|
||||
let vcpu_init = AArch64::vcpu_init(0, &payload, fdt_address, prot);
|
||||
|
||||
// PC: bios image entry point
|
||||
assert_eq!(vcpu_init.regs.get(&VcpuRegAArch64::Pc), Some(&0x8020_0000));
|
||||
|
@ -954,12 +966,15 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn vcpu_init_protected_kernel() {
|
||||
let has_bios = false;
|
||||
let payload = PayloadType::Kernel(LoadedKernel {
|
||||
address_range: AddressRange::from_start_and_size(0x8080_0000, 0x1000).unwrap(),
|
||||
size: 0x1000,
|
||||
entry: GuestAddress(0x8080_0000),
|
||||
});
|
||||
let fdt_address = GuestAddress(0x1234);
|
||||
let image_size = 0x1000;
|
||||
let prot = ProtectionType::Protected;
|
||||
|
||||
let vcpu_init = AArch64::vcpu_init(0, has_bios, fdt_address, image_size, prot);
|
||||
let vcpu_init = AArch64::vcpu_init(0, &payload, fdt_address, prot);
|
||||
|
||||
// The hypervisor provides the initial value of PC, so PC should not be present in the
|
||||
// vcpu_init register map.
|
||||
|
|
193
kernel_loader/src/arm64.rs
Normal file
193
kernel_loader/src/arm64.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2022 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Linux arm64 kernel loader.
|
||||
//! <https://www.kernel.org/doc/Documentation/arm64/booting.txt>
|
||||
|
||||
use std::io::Read;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
|
||||
use base::AsRawDescriptor;
|
||||
use data_model::DataInit;
|
||||
use data_model::Le32;
|
||||
use data_model::Le64;
|
||||
use resources::AddressRange;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::GuestMemory;
|
||||
|
||||
use crate::Error;
|
||||
use crate::LoadedKernel;
|
||||
use crate::Result;
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[allow(unused)]
|
||||
#[repr(C)]
|
||||
struct Arm64ImageHeader {
|
||||
code0: Le32,
|
||||
code1: Le32,
|
||||
text_offset: Le64,
|
||||
image_size: Le64,
|
||||
flags: Le64,
|
||||
res2: Le64,
|
||||
res3: Le64,
|
||||
res4: Le64,
|
||||
magic: Le32,
|
||||
res5: Le32,
|
||||
}
|
||||
|
||||
// Arm64ImageHeader is plain old data with no implicit padding.
|
||||
unsafe impl data_model::DataInit for Arm64ImageHeader {}
|
||||
|
||||
const ARM64_IMAGE_MAGIC: u32 = 0x644d5241; // "ARM\x64"
|
||||
|
||||
const ARM64_IMAGE_FLAG_BE_MASK: u64 = 0x1;
|
||||
|
||||
const ARM64_TEXT_OFFSET_DEFAULT: u64 = 0x80000;
|
||||
|
||||
pub fn load_arm64_kernel<F>(
|
||||
guest_mem: &GuestMemory,
|
||||
kernel_start: GuestAddress,
|
||||
mut kernel_image: &mut F,
|
||||
) -> Result<LoadedKernel>
|
||||
where
|
||||
F: Read + Seek + AsRawDescriptor,
|
||||
{
|
||||
kernel_image
|
||||
.seek(SeekFrom::Start(0))
|
||||
.map_err(|_| Error::SeekKernelStart)?;
|
||||
|
||||
let header = Arm64ImageHeader::from_reader(&mut kernel_image).map_err(|_| Error::ReadHeader)?;
|
||||
|
||||
let magic: u32 = header.magic.into();
|
||||
if magic != ARM64_IMAGE_MAGIC {
|
||||
return Err(Error::InvalidMagicNumber);
|
||||
}
|
||||
|
||||
let flags: u64 = header.flags.into();
|
||||
if flags & ARM64_IMAGE_FLAG_BE_MASK != 0 {
|
||||
return Err(Error::BigEndianOnLittle);
|
||||
}
|
||||
|
||||
let file_size = kernel_image
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|_| Error::SeekKernelEnd)?;
|
||||
kernel_image
|
||||
.seek(SeekFrom::Start(0))
|
||||
.map_err(|_| Error::SeekKernelStart)?;
|
||||
|
||||
let mut text_offset: u64 = header.text_offset.into();
|
||||
let image_size: u64 = header.image_size.into();
|
||||
|
||||
if image_size == 0 {
|
||||
// arm64/booting.txt: "Where image_size is zero, text_offset can be assumed to be 0x80000."
|
||||
text_offset = ARM64_TEXT_OFFSET_DEFAULT;
|
||||
}
|
||||
|
||||
// Load the image into guest memory at `text_offset` bytes past `kernel_start`.
|
||||
let load_addr = kernel_start
|
||||
.checked_add(text_offset)
|
||||
.ok_or(Error::InvalidKernelOffset)?;
|
||||
let load_size = usize::try_from(file_size).map_err(|_| Error::InvalidKernelSize)?;
|
||||
guest_mem
|
||||
.read_to_memory(load_addr, kernel_image, load_size)
|
||||
.map_err(|_| Error::ReadKernelImage)?;
|
||||
|
||||
Ok(LoadedKernel {
|
||||
size: file_size,
|
||||
address_range: AddressRange::from_start_and_size(load_addr.offset(), file_size)
|
||||
.ok_or(Error::InvalidKernelSize)?,
|
||||
entry: load_addr,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fs::File;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
|
||||
use tempfile::tempfile;
|
||||
use vm_memory::GuestAddress;
|
||||
use vm_memory::GuestMemory;
|
||||
|
||||
use crate::load_arm64_kernel;
|
||||
use crate::Error;
|
||||
|
||||
const MEM_SIZE: u64 = 0x200_0000;
|
||||
|
||||
fn create_guest_mem() -> GuestMemory {
|
||||
GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
|
||||
}
|
||||
|
||||
#[allow(clippy::unusual_byte_groupings)]
|
||||
fn write_valid_kernel() -> File {
|
||||
let mut f = tempfile().expect("failed to create tempfile");
|
||||
|
||||
f.write_all(&[0x00, 0xC0, 0x2E, 0x14]).unwrap(); // code0
|
||||
f.write_all(&[0x00, 0x00, 0x00, 0x00]).unwrap(); // code1
|
||||
f.write_all(&0x00000000_00E70000u64.to_le_bytes()).unwrap(); // text_offset
|
||||
f.write_all(&0x00000000_0000000Au64.to_le_bytes()).unwrap(); // image_size
|
||||
f.write_all(&0x00000000_00000000u64.to_le_bytes()).unwrap(); // flags
|
||||
f.write_all(&0x00000000_00000000u64.to_le_bytes()).unwrap(); // res2
|
||||
f.write_all(&0x00000000_00000000u64.to_le_bytes()).unwrap(); // res3
|
||||
f.write_all(&0x00000000_00000000u64.to_le_bytes()).unwrap(); // res4
|
||||
f.write_all(&0x644D5241u32.to_le_bytes()).unwrap(); // magic
|
||||
f.write_all(&0x00000000u32.to_le_bytes()).unwrap(); // res5
|
||||
|
||||
f.set_len(0xDC3808).unwrap();
|
||||
f
|
||||
}
|
||||
|
||||
fn mutate_file(mut f: &File, offset: u64, val: &[u8]) {
|
||||
f.seek(SeekFrom::Start(offset))
|
||||
.expect("failed to seek file");
|
||||
f.write_all(val)
|
||||
.expect("failed to write mutated value to file");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_arm64_valid() {
|
||||
let gm = create_guest_mem();
|
||||
let kernel_addr = GuestAddress(2 * 1024 * 1024);
|
||||
let mut f = write_valid_kernel();
|
||||
let kernel = load_arm64_kernel(&gm, kernel_addr, &mut f).unwrap();
|
||||
assert_eq!(kernel.address_range.start, 0x107_0000);
|
||||
assert_eq!(kernel.address_range.end, 0x1E3_3807);
|
||||
assert_eq!(kernel.size, 0xDC_3808);
|
||||
assert_eq!(kernel.entry, GuestAddress(0x107_0000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_arm64_image_size_zero() {
|
||||
let gm = create_guest_mem();
|
||||
let kernel_addr = GuestAddress(2 * 1024 * 1024);
|
||||
let mut f = write_valid_kernel();
|
||||
|
||||
// Set image_size = 0 and validate the default text_offset is applied.
|
||||
mutate_file(&f, 16, &0u64.to_le_bytes());
|
||||
|
||||
let kernel = load_arm64_kernel(&gm, kernel_addr, &mut f).unwrap();
|
||||
assert_eq!(kernel.address_range.start, 0x28_0000);
|
||||
assert_eq!(kernel.address_range.end, 0x104_3807);
|
||||
assert_eq!(kernel.size, 0xDC_3808);
|
||||
assert_eq!(kernel.entry, GuestAddress(0x28_0000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_arm64_bad_magic() {
|
||||
let gm = create_guest_mem();
|
||||
let kernel_addr = GuestAddress(2 * 1024 * 1024);
|
||||
let mut f = write_valid_kernel();
|
||||
|
||||
// Mutate magic number so it doesn't match
|
||||
mutate_file(&f, 56, &[0xCC, 0xCC, 0xCC, 0xCC]);
|
||||
|
||||
assert_eq!(
|
||||
load_arm64_kernel(&gm, kernel_addr, &mut f),
|
||||
Err(Error::InvalidMagicNumber)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -25,6 +25,10 @@ use vm_memory::GuestMemory;
|
|||
#[allow(clippy::all)]
|
||||
mod elf;
|
||||
|
||||
mod arm64;
|
||||
|
||||
pub use arm64::load_arm64_kernel;
|
||||
|
||||
// Elf32_Ehdr is plain old data with no implicit padding.
|
||||
unsafe impl data_model::DataInit for elf::Elf32_Ehdr {}
|
||||
|
||||
|
@ -52,6 +56,10 @@ pub enum Error {
|
|||
InvalidElfVersion,
|
||||
#[error("invalid entry point")]
|
||||
InvalidEntryPoint,
|
||||
#[error("invalid kernel offset")]
|
||||
InvalidKernelOffset,
|
||||
#[error("invalid kernel size")]
|
||||
InvalidKernelSize,
|
||||
#[error("invalid magic number")]
|
||||
InvalidMagicNumber,
|
||||
#[error("invalid Program Header Address")]
|
||||
|
@ -72,6 +80,8 @@ pub enum Error {
|
|||
ReadKernelImage,
|
||||
#[error("unable to read program header")]
|
||||
ReadProgramHeader,
|
||||
#[error("unable to seek to kernel end")]
|
||||
SeekKernelEnd,
|
||||
#[error("unable to seek to kernel start")]
|
||||
SeekKernelStart,
|
||||
#[error("unable to seek to program header")]
|
||||
|
|
Loading…
Reference in a new issue