// Copyright 2017 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. //! Linux kernel ELF file loader. use std::ffi::CStr; use std::io::Read; use std::io::Seek; use std::io::SeekFrom; use std::mem; use base::AsRawDescriptor; use data_model::zerocopy_from_reader; use remain::sorted; use resources::AddressRange; use thiserror::Error; use vm_memory::GuestAddress; use vm_memory::GuestMemory; use zerocopy::FromBytes; #[allow(dead_code)] #[allow(non_camel_case_types)] #[allow(non_snake_case)] #[allow(non_upper_case_globals)] #[allow(clippy::all)] mod elf; mod arm64; pub use arm64::load_arm64_kernel; #[sorted] #[derive(Error, Debug, PartialEq, Eq)] pub enum Error { #[error("trying to load big-endian binary on little-endian machine")] BigEndianOnLittle, #[error("failed writing command line to guest memory")] CommandLineCopy, #[error("command line overflowed guest memory")] CommandLineOverflow, #[error("invalid elf class")] InvalidElfClass, #[error("invalid elf version")] 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")] InvalidProgramHeaderAddress, #[error("invalid Program Header memory size")] InvalidProgramHeaderMemSize, #[error("invalid program header offset")] InvalidProgramHeaderOffset, #[error("invalid program header size")] InvalidProgramHeaderSize, #[error("no loadable program headers found")] NoLoadableProgramHeaders, #[error("program header address out of allowed address range")] ProgramHeaderAddressOutOfRange, #[error("unable to read header")] ReadHeader, #[error("unable to read kernel image")] 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")] SeekProgramHeader, } pub type Result = std::result::Result; #[derive(Debug, Copy, Clone, PartialEq, Eq)] /// Information about a kernel loaded with the [`load_elf`] function. pub struct LoadedKernel { /// Address range containg the bounds of the loaded program headers. /// `address_range.start` is the start of the lowest loaded program header. /// `address_range.end` is the end of the highest loaded program header. pub address_range: AddressRange, /// Size of the kernel image in bytes. pub size: u64, /// Entry point address of the kernel. pub entry: GuestAddress, } /// Loads a kernel from a 32-bit ELF image into memory. /// /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its /// program headers. /// /// # Arguments /// /// * `guest_mem` - The guest memory region the kernel is written to. /// * `kernel_start` - The minimum guest address to allow when loading program headers. /// * `kernel_image` - Input vmlinux image. /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`). pub fn load_elf32( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result where F: Read + Seek + AsRawDescriptor, { load_elf_for_class( guest_mem, kernel_start, kernel_image, phys_offset, Some(elf::ELFCLASS32), ) } /// Loads a kernel from a 64-bit ELF image into memory. /// /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its /// program headers. /// /// # Arguments /// /// * `guest_mem` - The guest memory region the kernel is written to. /// * `kernel_start` - The minimum guest address to allow when loading program headers. /// * `kernel_image` - Input vmlinux image. /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`). pub fn load_elf64( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result where F: Read + Seek + AsRawDescriptor, { load_elf_for_class( guest_mem, kernel_start, kernel_image, phys_offset, Some(elf::ELFCLASS64), ) } /// Loads a kernel from a 32-bit or 64-bit ELF image into memory. /// /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its /// program headers. /// /// # Arguments /// /// * `guest_mem` - The guest memory region the kernel is written to. /// * `kernel_start` - The minimum guest address to allow when loading program headers. /// * `kernel_image` - Input vmlinux image. /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`). pub fn load_elf( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result where F: Read + Seek + AsRawDescriptor, { load_elf_for_class(guest_mem, kernel_start, kernel_image, phys_offset, None) } fn load_elf_for_class( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ei_class: Option, ) -> Result where F: Read + Seek + AsRawDescriptor, { let elf = read_elf(kernel_image, ei_class)?; let mut start = None; let mut end = 0; // Read in each section pointed to by the program headers. for phdr in &elf.program_headers { if phdr.p_type != elf::PT_LOAD { continue; } let paddr = phdr .p_paddr .checked_add(phys_offset) .ok_or(Error::ProgramHeaderAddressOutOfRange)?; if paddr < kernel_start.offset() { return Err(Error::ProgramHeaderAddressOutOfRange); } if start.is_none() { start = Some(paddr); } end = paddr .checked_add(phdr.p_memsz) .ok_or(Error::InvalidProgramHeaderMemSize)?; if phdr.p_filesz == 0 { continue; } kernel_image .seek(SeekFrom::Start(phdr.p_offset)) .map_err(|_| Error::SeekKernelStart)?; guest_mem .read_to_memory(GuestAddress(paddr), kernel_image, phdr.p_filesz as usize) .map_err(|_| Error::ReadKernelImage)?; } // We should have found at least one PT_LOAD program header. If not, `start` will not be set. let start = start.ok_or(Error::NoLoadableProgramHeaders)?; let size = end .checked_sub(start) .ok_or(Error::InvalidProgramHeaderSize)?; let address_range = AddressRange { start, end }; // The entry point address must fall within one of the loaded sections. // We approximate this by checking whether it within the bounds of the first and last sections. let entry = elf .file_header .e_entry .checked_add(phys_offset) .ok_or(Error::InvalidEntryPoint)?; if !address_range.contains(entry) { return Err(Error::InvalidEntryPoint); } Ok(LoadedKernel { address_range, size, entry: GuestAddress(entry), }) } /// Writes the command line string to the given memory slice. /// /// # Arguments /// /// * `guest_mem` - A u8 slice that will be partially overwritten by the command line. /// * `guest_addr` - The address in `guest_mem` at which to load the command line. /// * `cmdline` - The kernel command line. pub fn load_cmdline( guest_mem: &GuestMemory, guest_addr: GuestAddress, cmdline: &CStr, ) -> Result<()> { let len = cmdline.to_bytes().len(); if len == 0 { return Ok(()); } let end = guest_addr .checked_add(len as u64 + 1) .ok_or(Error::CommandLineOverflow)?; // Extra for null termination. if end > guest_mem.end_addr() { return Err(Error::CommandLineOverflow); } guest_mem .write_at_addr(cmdline.to_bytes_with_nul(), guest_addr) .map_err(|_| Error::CommandLineCopy)?; Ok(()) } struct Elf64 { file_header: elf::Elf64_Ehdr, program_headers: Vec, } /// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers, /// converted to ELF64 format. If `required_ei_class` is Some and the file's ELF ei_class doesn't /// match, an Err is returned. fn read_elf(file: &mut F, required_ei_class: Option) -> Result where F: Read + Seek + AsRawDescriptor, { // Read the ELF identification (e_ident) block. file.seek(SeekFrom::Start(0)) .map_err(|_| Error::SeekKernelStart)?; let mut ident = [0u8; 16]; file.read_exact(&mut ident).map_err(|_| Error::ReadHeader)?; // e_ident checks if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8 || ident[elf::EI_MAG1 as usize] != elf::ELFMAG1 || ident[elf::EI_MAG2 as usize] != elf::ELFMAG2 || ident[elf::EI_MAG3 as usize] != elf::ELFMAG3 { return Err(Error::InvalidMagicNumber); } if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 { return Err(Error::BigEndianOnLittle); } if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 { return Err(Error::InvalidElfVersion); } let ei_class = ident[elf::EI_CLASS as usize] as u32; if let Some(required_ei_class) = required_ei_class { if ei_class != required_ei_class { return Err(Error::InvalidElfClass); } } match ei_class { elf::ELFCLASS32 => read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file), elf::ELFCLASS64 => read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file), _ => Err(Error::InvalidElfClass), } } /// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers, /// converted to ELF64 format. `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr /// types to read from the file. Caller should check that `file` is a valid ELF file before calling /// this function. fn read_elf_by_type(mut file: &mut F) -> Result where F: Read + Seek + AsRawDescriptor, FileHeader: FromBytes + Default + Into, ProgramHeader: FromBytes + Default + Into, { file.seek(SeekFrom::Start(0)) .map_err(|_| Error::SeekKernelStart)?; let ehdr: FileHeader = zerocopy_from_reader(&mut file).map_err(|_| Error::ReadHeader)?; let ehdr: elf::Elf64_Ehdr = ehdr.into(); if ehdr.e_phentsize as usize != mem::size_of::() { return Err(Error::InvalidProgramHeaderSize); } if (ehdr.e_phoff as usize) < mem::size_of::() { // If the program header is backwards, bail. return Err(Error::InvalidProgramHeaderOffset); } file.seek(SeekFrom::Start(ehdr.e_phoff as u64)) .map_err(|_| Error::SeekProgramHeader)?; let phdrs: Vec = (0..ehdr.e_phnum) .enumerate() .map(|_| zerocopy_from_reader(&mut file).map_err(|_| Error::ReadProgramHeader)) .collect::>>()?; Ok(Elf64 { file_header: ehdr, program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(), }) } impl From for elf::Elf64_Ehdr { fn from(ehdr32: elf::Elf32_Ehdr) -> Self { elf::Elf64_Ehdr { e_ident: ehdr32.e_ident, e_type: ehdr32.e_type as elf::Elf64_Half, e_machine: ehdr32.e_machine as elf::Elf64_Half, e_version: ehdr32.e_version as elf::Elf64_Word, e_entry: ehdr32.e_entry as elf::Elf64_Addr, e_phoff: ehdr32.e_phoff as elf::Elf64_Off, e_shoff: ehdr32.e_shoff as elf::Elf64_Off, e_flags: ehdr32.e_flags as elf::Elf64_Word, e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half, e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half, e_phnum: ehdr32.e_phnum as elf::Elf64_Half, e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half, e_shnum: ehdr32.e_shnum as elf::Elf64_Half, e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half, } } } impl From for elf::Elf64_Phdr { fn from(phdr32: elf::Elf32_Phdr) -> Self { elf::Elf64_Phdr { p_type: phdr32.p_type as elf::Elf64_Word, p_flags: phdr32.p_flags as elf::Elf64_Word, p_offset: phdr32.p_offset as elf::Elf64_Off, p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr, p_paddr: phdr32.p_paddr as elf::Elf64_Addr, p_filesz: phdr32.p_filesz as elf::Elf64_Xword, p_memsz: phdr32.p_memsz as elf::Elf64_Xword, p_align: phdr32.p_align as elf::Elf64_Xword, } } } #[cfg(test)] mod test { use std::fs::File; use std::io::Write; use tempfile::tempfile; use vm_memory::GuestAddress; use vm_memory::GuestMemory; use super::*; const MEM_SIZE: u64 = 0x40_0000; fn create_guest_mem() -> GuestMemory { GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap() } #[test] fn cmdline_overflow() { let gm = create_guest_mem(); let cmdline_address = GuestAddress(MEM_SIZE - 5); assert_eq!( Err(Error::CommandLineOverflow), load_cmdline( &gm, cmdline_address, CStr::from_bytes_with_nul(b"12345\0").unwrap() ) ); } #[test] fn cmdline_write_end() { let gm = create_guest_mem(); let mut cmdline_address = GuestAddress(45); assert_eq!( Ok(()), load_cmdline( &gm, cmdline_address, CStr::from_bytes_with_nul(b"1234\0").unwrap() ) ); let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap(); assert_eq!(val, b'1'); cmdline_address = cmdline_address.unchecked_add(1); let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap(); assert_eq!(val, b'2'); cmdline_address = cmdline_address.unchecked_add(1); let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap(); assert_eq!(val, b'3'); cmdline_address = cmdline_address.unchecked_add(1); let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap(); assert_eq!(val, b'4'); cmdline_address = cmdline_address.unchecked_add(1); let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap(); assert_eq!(val, b'\0'); } // Elf32 image that prints hello world on x86. fn make_elf32_bin() -> File { // test_elf32.bin built on Linux with gcc -m32 -static-pie let bytes = include_bytes!("test_elf32.bin"); make_elf_bin(bytes) } // Elf64 image that prints hello world on x86_64. fn make_elf64_bin() -> File { let bytes = include_bytes!("test_elf64.bin"); make_elf_bin(bytes) } fn make_elf_bin(elf_bytes: &[u8]) -> File { let mut file = tempfile().expect("failed to create tempfile"); file.write_all(elf_bytes) .expect("failed to write elf to shared memory"); file } fn mutate_elf_bin(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_elf32() { let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut image = make_elf32_bin(); let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap(); assert_eq!(kernel.address_range.start, 0); assert_eq!(kernel.address_range.end, 0xa_2038); assert_eq!(kernel.size, 0xa_2038); assert_eq!(kernel.entry, GuestAddress(0x3dc0)); } #[test] fn load_elf64() { let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut image = make_elf64_bin(); let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF"); assert_eq!(kernel.address_range.start, 0x20_0000); assert_eq!(kernel.address_range.end, 0x20_0035); assert_eq!(kernel.size, 0x35); assert_eq!(kernel.entry, GuestAddress(0x20_000e)); } #[test] fn bad_magic() { let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut bad_image = make_elf64_bin(); mutate_elf_bin(&bad_image, 0x1, 0x33); assert_eq!( Err(Error::InvalidMagicNumber), load_elf(&gm, kernel_addr, &mut bad_image, 0) ); } #[test] fn bad_endian() { // Only little endian is supported let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x20_0000); let mut bad_image = make_elf64_bin(); mutate_elf_bin(&bad_image, 0x5, 2); assert_eq!( Err(Error::BigEndianOnLittle), load_elf(&gm, kernel_addr, &mut bad_image, 0) ); } #[test] fn bad_phoff() { // program header has to be past the end of the elf header let gm = create_guest_mem(); let kernel_addr = GuestAddress(0x0); let mut bad_image = make_elf64_bin(); mutate_elf_bin(&bad_image, 0x20, 0x10); assert_eq!( Err(Error::InvalidProgramHeaderOffset), load_elf(&gm, kernel_addr, &mut bad_image, 0) ); } #[test] fn paddr_below_start() { let gm = create_guest_mem(); // test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address let kernel_addr = GuestAddress(0x30_0000); let mut image = make_elf64_bin(); let res = load_elf(&gm, kernel_addr, &mut image, 0); assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange)); } }