| // Copyright 2017 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. | |
| //! 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::DataInit; | |
| use remain::sorted; | |
| use resources::AddressRange; | |
| use thiserror::Error; | |
| use vm_memory::GuestAddress; | |
| use vm_memory::GuestMemory; | |
| #[allow(dead_code)] | |
| #[allow(non_camel_case_types)] | |
| #[allow(non_snake_case)] | |
| #[allow(non_upper_case_globals)] | |
| #[allow(clippy::all)] | |
| mod elf; | |
| // Elf32_Ehdr is plain old data with no implicit padding. | |
| unsafe impl data_model::DataInit for elf::Elf32_Ehdr {} | |
| // Elf32_Phdr is plain old data with no implicit padding. | |
| unsafe impl data_model::DataInit for elf::Elf32_Phdr {} | |
| // Elf64_Ehdr is plain old data with no implicit padding. | |
| unsafe impl data_model::DataInit for elf::Elf64_Ehdr {} | |
| // Elf64_Phdr is plain old data with no implicit padding. | |
| unsafe impl data_model::DataInit for elf::Elf64_Phdr {} | |
| #[sorted] | |
| #[derive(Error, Debug, PartialEq)] | |
| pub enum Error { | |
| #[error("trying to load big-endian binary on little-endian machine")] | |
| BigEndianElfOnLittle, | |
| #[error("failed writing command line to guest memory")] | |
| CommandLineCopy, | |
| #[error("command line overflowed guest memory")] | |
| CommandLineOverflow, | |
| #[error("invalid elf class")] | |
| InvalidElfClass, | |
| #[error("invalid Elf magic number")] | |
| InvalidElfMagicNumber, | |
| #[error("invalid elf version")] | |
| InvalidElfVersion, | |
| #[error("invalid entry point")] | |
| InvalidEntryPoint, | |
| #[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 elf header")] | |
| ReadElfHeader, | |
| #[error("unable to read kernel image")] | |
| ReadKernelImage, | |
| #[error("unable to read program header")] | |
| ReadProgramHeader, | |
| #[error("unable to seek to elf start")] | |
| SeekElfStart, | |
| #[error("unable to seek to kernel start")] | |
| SeekKernelStart, | |
| #[error("unable to seek to program header")] | |
| SeekProgramHeader, | |
| } | |
| pub type Result<T> = std::result::Result<T, Error>; | |
| #[derive(Debug, Copy, Clone, PartialEq)] | |
| /// Information about a kernel loaded with the [`load_kernel`] 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. | |
| pub fn load_elf32<F>( | |
| guest_mem: &GuestMemory, | |
| kernel_start: GuestAddress, | |
| kernel_image: &mut F, | |
| ) -> Result<LoadedKernel> | |
| where | |
| F: Read + Seek + AsRawDescriptor, | |
| { | |
| load_elf_for_class(guest_mem, kernel_start, kernel_image, 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. | |
| pub fn load_elf64<F>( | |
| guest_mem: &GuestMemory, | |
| kernel_start: GuestAddress, | |
| kernel_image: &mut F, | |
| ) -> Result<LoadedKernel> | |
| where | |
| F: Read + Seek + AsRawDescriptor, | |
| { | |
| load_elf_for_class(guest_mem, kernel_start, kernel_image, 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. | |
| pub fn load_elf<F>( | |
| guest_mem: &GuestMemory, | |
| kernel_start: GuestAddress, | |
| kernel_image: &mut F, | |
| ) -> Result<LoadedKernel> | |
| where | |
| F: Read + Seek + AsRawDescriptor, | |
| { | |
| load_elf_for_class(guest_mem, kernel_start, kernel_image, None) | |
| } | |
| fn load_elf_for_class<F>( | |
| guest_mem: &GuestMemory, | |
| kernel_start: GuestAddress, | |
| kernel_image: &mut F, | |
| ei_class: Option<u32>, | |
| ) -> Result<LoadedKernel> | |
| 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; | |
| } | |
| if phdr.p_paddr < kernel_start.offset() { | |
| return Err(Error::ProgramHeaderAddressOutOfRange); | |
| } | |
| if start.is_none() { | |
| start = Some(phdr.p_paddr); | |
| } | |
| end = phdr | |
| .p_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(phdr.p_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 }; | |
| // `e_entry` of 0 means there is no entry point, which we do not want to allow. | |
| // The entry point address must also fall within one of the loaded sections. | |
| // We approximate this by checking whether it within the bounds of the first and last sections. | |
| if elf.file_header.e_entry == 0 || !address_range.contains(elf.file_header.e_entry) { | |
| return Err(Error::InvalidEntryPoint); | |
| } | |
| Ok(LoadedKernel { | |
| address_range, | |
| size, | |
| entry: GuestAddress(elf.file_header.e_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<elf::Elf64_Phdr>, | |
| } | |
| /// 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<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64> | |
| where | |
| F: Read + Seek + AsRawDescriptor, | |
| { | |
| // Read the ELF identification (e_ident) block. | |
| file.seek(SeekFrom::Start(0)) | |
| .map_err(|_| Error::SeekElfStart)?; | |
| let mut ident = [0u8; 16]; | |
| file.read_exact(&mut ident) | |
| .map_err(|_| Error::ReadElfHeader)?; | |
| // 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::InvalidElfMagicNumber); | |
| } | |
| if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 { | |
| return Err(Error::BigEndianElfOnLittle); | |
| } | |
| 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<F, FileHeader, ProgramHeader>(mut file: &mut F) -> Result<Elf64> | |
| where | |
| F: Read + Seek + AsRawDescriptor, | |
| FileHeader: DataInit + Default + Into<elf::Elf64_Ehdr>, | |
| ProgramHeader: DataInit + Default + Into<elf::Elf64_Phdr>, | |
| { | |
| file.seek(SeekFrom::Start(0)) | |
| .map_err(|_| Error::SeekElfStart)?; | |
| let ehdr: FileHeader = FileHeader::from_reader(&mut file).map_err(|_| Error::ReadElfHeader)?; | |
| let ehdr: elf::Elf64_Ehdr = ehdr.into(); | |
| if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() { | |
| return Err(Error::InvalidProgramHeaderSize); | |
| } | |
| if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() { | |
| // 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<ProgramHeader> = (0..ehdr.e_phnum) | |
| .enumerate() | |
| .map(|_| ProgramHeader::from_reader(&mut file).map_err(|_| Error::ReadProgramHeader)) | |
| .collect::<Result<Vec<ProgramHeader>>>()?; | |
| Ok(Elf64 { | |
| file_header: ehdr, | |
| program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(), | |
| }) | |
| } | |
| impl From<elf::Elf32_Ehdr> 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<elf::Elf32_Phdr> 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).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).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::InvalidElfMagicNumber), | |
| load_elf(&gm, kernel_addr, &mut bad_image) | |
| ); | |
| } | |
| #[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::BigEndianElfOnLittle), | |
| load_elf(&gm, kernel_addr, &mut bad_image) | |
| ); | |
| } | |
| #[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) | |
| ); | |
| } | |
| #[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); | |
| assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange)); | |
| } | |
| } |