xref: /aosp_15_r20/external/crosvm/kernel_loader/src/lib.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
1 // Copyright 2017 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 //! Linux kernel ELF file loader.
6 
7 use std::mem;
8 
9 use base::FileReadWriteAtVolatile;
10 use base::VolatileSlice;
11 use remain::sorted;
12 use resources::AddressRange;
13 use thiserror::Error;
14 use vm_memory::GuestAddress;
15 use vm_memory::GuestMemory;
16 use zerocopy::AsBytes;
17 use zerocopy::FromBytes;
18 
19 mod multiboot;
20 
21 #[allow(dead_code)]
22 #[allow(non_camel_case_types)]
23 #[allow(non_snake_case)]
24 #[allow(non_upper_case_globals)]
25 #[allow(clippy::all)]
26 mod elf;
27 
28 mod arm64;
29 
30 pub use arm64::load_arm64_kernel;
31 pub use arm64::load_arm64_kernel_lz4;
32 pub use multiboot::load_multiboot;
33 pub use multiboot::multiboot_header_from_file;
34 
35 #[sorted]
36 #[derive(Error, Debug, PartialEq, Eq)]
37 pub enum Error {
38     #[error("trying to load big-endian binary on little-endian machine")]
39     BigEndianOnLittle,
40     #[error("invalid elf class")]
41     InvalidElfClass,
42     #[error("invalid elf version")]
43     InvalidElfVersion,
44     #[error("invalid entry point")]
45     InvalidEntryPoint,
46     #[error("invalid flags")]
47     InvalidFlags,
48     #[error("invalid kernel offset")]
49     InvalidKernelOffset,
50     #[error("invalid kernel size")]
51     InvalidKernelSize,
52     #[error("invalid magic number")]
53     InvalidMagicNumber,
54     #[error("invalid Program Header Address")]
55     InvalidProgramHeaderAddress,
56     #[error("invalid Program Header memory size")]
57     InvalidProgramHeaderMemSize,
58     #[error("invalid program header offset")]
59     InvalidProgramHeaderOffset,
60     #[error("invalid program header size")]
61     InvalidProgramHeaderSize,
62     #[error("no loadable program headers found")]
63     NoLoadableProgramHeaders,
64     #[error("program header address out of allowed address range")]
65     ProgramHeaderAddressOutOfRange,
66     #[error("unable to read header")]
67     ReadHeader,
68     #[error("unable to read kernel image")]
69     ReadKernelImage,
70     #[error("unable to read program header")]
71     ReadProgramHeader,
72     #[error("unable to seek to kernel end")]
73     SeekKernelEnd,
74     #[error("unable to seek to kernel start")]
75     SeekKernelStart,
76     #[error("unable to seek to program header")]
77     SeekProgramHeader,
78 }
79 pub type Result<T> = std::result::Result<T, Error>;
80 
81 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
82 /// Information about a kernel loaded with the [`load_elf`] function.
83 pub struct LoadedKernel {
84     /// Address range containg the bounds of the loaded program headers.
85     /// `address_range.start` is the start of the lowest loaded program header.
86     /// `address_range.end` is the end of the highest loaded program header.
87     pub address_range: AddressRange,
88 
89     /// Size of the kernel image in bytes.
90     pub size: u64,
91 
92     /// Entry point address of the kernel.
93     pub entry: GuestAddress,
94 }
95 
96 /// Loads a kernel from a 32-bit ELF image into memory.
97 ///
98 /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
99 /// program headers.
100 ///
101 /// # Arguments
102 ///
103 /// * `guest_mem` - The guest memory region the kernel is written to.
104 /// * `kernel_start` - The minimum guest address to allow when loading program headers.
105 /// * `kernel_image` - Input vmlinux image.
106 /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
load_elf32<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result<LoadedKernel> where F: FileReadWriteAtVolatile,107 pub fn load_elf32<F>(
108     guest_mem: &GuestMemory,
109     kernel_start: GuestAddress,
110     kernel_image: &mut F,
111     phys_offset: u64,
112 ) -> Result<LoadedKernel>
113 where
114     F: FileReadWriteAtVolatile,
115 {
116     load_elf_for_class(
117         guest_mem,
118         kernel_start,
119         kernel_image,
120         phys_offset,
121         Some(elf::ELFCLASS32),
122     )
123 }
124 
125 /// Loads a kernel from a 64-bit ELF image into memory.
126 ///
127 /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
128 /// program headers.
129 ///
130 /// # Arguments
131 ///
132 /// * `guest_mem` - The guest memory region the kernel is written to.
133 /// * `kernel_start` - The minimum guest address to allow when loading program headers.
134 /// * `kernel_image` - Input vmlinux image.
135 /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
load_elf64<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result<LoadedKernel> where F: FileReadWriteAtVolatile,136 pub fn load_elf64<F>(
137     guest_mem: &GuestMemory,
138     kernel_start: GuestAddress,
139     kernel_image: &mut F,
140     phys_offset: u64,
141 ) -> Result<LoadedKernel>
142 where
143     F: FileReadWriteAtVolatile,
144 {
145     load_elf_for_class(
146         guest_mem,
147         kernel_start,
148         kernel_image,
149         phys_offset,
150         Some(elf::ELFCLASS64),
151     )
152 }
153 
154 /// Loads a kernel from a 32-bit or 64-bit ELF image into memory.
155 ///
156 /// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
157 /// program headers.
158 ///
159 /// # Arguments
160 ///
161 /// * `guest_mem` - The guest memory region the kernel is written to.
162 /// * `kernel_start` - The minimum guest address to allow when loading program headers.
163 /// * `kernel_image` - Input vmlinux image.
164 /// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
load_elf<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ) -> Result<LoadedKernel> where F: FileReadWriteAtVolatile,165 pub fn load_elf<F>(
166     guest_mem: &GuestMemory,
167     kernel_start: GuestAddress,
168     kernel_image: &mut F,
169     phys_offset: u64,
170 ) -> Result<LoadedKernel>
171 where
172     F: FileReadWriteAtVolatile,
173 {
174     load_elf_for_class(guest_mem, kernel_start, kernel_image, phys_offset, None)
175 }
176 
load_elf_for_class<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, phys_offset: u64, ei_class: Option<u32>, ) -> Result<LoadedKernel> where F: FileReadWriteAtVolatile,177 fn load_elf_for_class<F>(
178     guest_mem: &GuestMemory,
179     kernel_start: GuestAddress,
180     kernel_image: &mut F,
181     phys_offset: u64,
182     ei_class: Option<u32>,
183 ) -> Result<LoadedKernel>
184 where
185     F: FileReadWriteAtVolatile,
186 {
187     let elf = read_elf(kernel_image, ei_class)?;
188     let mut start = None;
189     let mut end = 0;
190 
191     // Read in each section pointed to by the program headers.
192     for phdr in &elf.program_headers {
193         if phdr.p_type != elf::PT_LOAD {
194             continue;
195         }
196 
197         let paddr = phdr
198             .p_paddr
199             .checked_add(phys_offset)
200             .ok_or(Error::ProgramHeaderAddressOutOfRange)?;
201 
202         if paddr < kernel_start.offset() {
203             return Err(Error::ProgramHeaderAddressOutOfRange);
204         }
205 
206         if start.is_none() {
207             start = Some(paddr);
208         }
209 
210         end = paddr
211             .checked_add(phdr.p_memsz)
212             .ok_or(Error::InvalidProgramHeaderMemSize)?;
213 
214         if phdr.p_filesz == 0 {
215             continue;
216         }
217 
218         let guest_slice = guest_mem
219             .get_slice_at_addr(GuestAddress(paddr), phdr.p_filesz as usize)
220             .map_err(|_| Error::ReadKernelImage)?;
221         kernel_image
222             .read_exact_at_volatile(guest_slice, phdr.p_offset)
223             .map_err(|_| Error::ReadKernelImage)?;
224     }
225 
226     // We should have found at least one PT_LOAD program header. If not, `start` will not be set.
227     let start = start.ok_or(Error::NoLoadableProgramHeaders)?;
228 
229     let size = end
230         .checked_sub(start)
231         .ok_or(Error::InvalidProgramHeaderSize)?;
232 
233     let address_range = AddressRange { start, end };
234 
235     // The entry point address must fall within one of the loaded sections.
236     // We approximate this by checking whether it within the bounds of the first and last sections.
237     let entry = elf
238         .file_header
239         .e_entry
240         .checked_add(phys_offset)
241         .ok_or(Error::InvalidEntryPoint)?;
242     if !address_range.contains(entry) {
243         return Err(Error::InvalidEntryPoint);
244     }
245 
246     Ok(LoadedKernel {
247         address_range,
248         size,
249         entry: GuestAddress(entry),
250     })
251 }
252 
253 struct Elf64 {
254     file_header: elf::Elf64_Ehdr,
255     program_headers: Vec<elf::Elf64_Phdr>,
256 }
257 
258 /// Reads the headers of an ELF32 or ELF64 object file.  Returns ELF file and program headers,
259 /// converted to ELF64 format.  If `required_ei_class` is Some and the file's ELF ei_class doesn't
260 /// match, an Err is returned.
read_elf<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64> where F: FileReadWriteAtVolatile,261 fn read_elf<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64>
262 where
263     F: FileReadWriteAtVolatile,
264 {
265     // Read the ELF identification (e_ident) block.
266     let mut ident = [0u8; 16];
267     file.read_exact_at_volatile(VolatileSlice::new(&mut ident), 0)
268         .map_err(|_| Error::ReadHeader)?;
269 
270     // e_ident checks
271     if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
272         || ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
273         || ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
274         || ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
275     {
276         return Err(Error::InvalidMagicNumber);
277     }
278     if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
279         return Err(Error::BigEndianOnLittle);
280     }
281     if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 {
282         return Err(Error::InvalidElfVersion);
283     }
284 
285     let ei_class = ident[elf::EI_CLASS as usize] as u32;
286     if let Some(required_ei_class) = required_ei_class {
287         if ei_class != required_ei_class {
288             return Err(Error::InvalidElfClass);
289         }
290     }
291     match ei_class {
292         elf::ELFCLASS32 => read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file),
293         elf::ELFCLASS64 => read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file),
294         _ => Err(Error::InvalidElfClass),
295     }
296 }
297 
298 /// Reads the headers of an ELF32 or ELF64 object file.  Returns ELF file and program headers,
299 /// converted to ELF64 format.  `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr
300 /// types to read from the file.  Caller should check that `file` is a valid ELF file before calling
301 /// this function.
read_elf_by_type<F, FileHeader, ProgramHeader>(file: &mut F) -> Result<Elf64> where F: FileReadWriteAtVolatile, FileHeader: AsBytes + FromBytes + Default + Into<elf::Elf64_Ehdr>, ProgramHeader: AsBytes + FromBytes + Clone + Default + Into<elf::Elf64_Phdr>,302 fn read_elf_by_type<F, FileHeader, ProgramHeader>(file: &mut F) -> Result<Elf64>
303 where
304     F: FileReadWriteAtVolatile,
305     FileHeader: AsBytes + FromBytes + Default + Into<elf::Elf64_Ehdr>,
306     ProgramHeader: AsBytes + FromBytes + Clone + Default + Into<elf::Elf64_Phdr>,
307 {
308     let mut ehdr = FileHeader::new_zeroed();
309     file.read_exact_at_volatile(VolatileSlice::new(ehdr.as_bytes_mut()), 0)
310         .map_err(|_| Error::ReadHeader)?;
311     let ehdr: elf::Elf64_Ehdr = ehdr.into();
312 
313     if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() {
314         return Err(Error::InvalidProgramHeaderSize);
315     }
316     if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() {
317         // If the program header is backwards, bail.
318         return Err(Error::InvalidProgramHeaderOffset);
319     }
320 
321     let num_phdrs = ehdr.e_phnum as usize;
322     let mut phdrs = vec![ProgramHeader::default(); num_phdrs];
323     file.read_exact_at_volatile(VolatileSlice::new(phdrs.as_bytes_mut()), ehdr.e_phoff)
324         .map_err(|_| Error::ReadProgramHeader)?;
325 
326     Ok(Elf64 {
327         file_header: ehdr,
328         program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(),
329     })
330 }
331 
332 impl From<elf::Elf32_Ehdr> for elf::Elf64_Ehdr {
from(ehdr32: elf::Elf32_Ehdr) -> Self333     fn from(ehdr32: elf::Elf32_Ehdr) -> Self {
334         elf::Elf64_Ehdr {
335             e_ident: ehdr32.e_ident,
336             e_type: ehdr32.e_type as elf::Elf64_Half,
337             e_machine: ehdr32.e_machine as elf::Elf64_Half,
338             e_version: ehdr32.e_version as elf::Elf64_Word,
339             e_entry: ehdr32.e_entry as elf::Elf64_Addr,
340             e_phoff: ehdr32.e_phoff as elf::Elf64_Off,
341             e_shoff: ehdr32.e_shoff as elf::Elf64_Off,
342             e_flags: ehdr32.e_flags as elf::Elf64_Word,
343             e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half,
344             e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half,
345             e_phnum: ehdr32.e_phnum as elf::Elf64_Half,
346             e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half,
347             e_shnum: ehdr32.e_shnum as elf::Elf64_Half,
348             e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half,
349         }
350     }
351 }
352 
353 impl From<elf::Elf32_Phdr> for elf::Elf64_Phdr {
from(phdr32: elf::Elf32_Phdr) -> Self354     fn from(phdr32: elf::Elf32_Phdr) -> Self {
355         elf::Elf64_Phdr {
356             p_type: phdr32.p_type as elf::Elf64_Word,
357             p_flags: phdr32.p_flags as elf::Elf64_Word,
358             p_offset: phdr32.p_offset as elf::Elf64_Off,
359             p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr,
360             p_paddr: phdr32.p_paddr as elf::Elf64_Addr,
361             p_filesz: phdr32.p_filesz as elf::Elf64_Xword,
362             p_memsz: phdr32.p_memsz as elf::Elf64_Xword,
363             p_align: phdr32.p_align as elf::Elf64_Xword,
364         }
365     }
366 }
367 
368 #[cfg(test)]
369 mod test {
370     use std::fs::File;
371     use std::io::Seek;
372     use std::io::SeekFrom;
373     use std::io::Write;
374 
375     use tempfile::tempfile;
376     use vm_memory::GuestAddress;
377     use vm_memory::GuestMemory;
378 
379     use super::*;
380 
381     const MEM_SIZE: u64 = 0x40_0000;
382 
create_guest_mem() -> GuestMemory383     fn create_guest_mem() -> GuestMemory {
384         GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
385     }
386 
387     // Elf32 image that prints hello world on x86.
make_elf32_bin() -> File388     fn make_elf32_bin() -> File {
389         // test_elf32.bin built on Linux with gcc -m32 -static-pie
390         let bytes = include_bytes!("test_elf32.bin");
391         make_elf_bin(bytes)
392     }
393 
394     // Elf64 image that prints hello world on x86_64.
make_elf64_bin() -> File395     fn make_elf64_bin() -> File {
396         let bytes = include_bytes!("test_elf64.bin");
397         make_elf_bin(bytes)
398     }
399 
make_elf_bin(elf_bytes: &[u8]) -> File400     fn make_elf_bin(elf_bytes: &[u8]) -> File {
401         let mut file = tempfile().expect("failed to create tempfile");
402         file.write_all(elf_bytes)
403             .expect("failed to write elf to shared memory");
404         file
405     }
406 
mutate_elf_bin(mut f: &File, offset: u64, val: u8)407     fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
408         f.seek(SeekFrom::Start(offset))
409             .expect("failed to seek file");
410         f.write_all(&[val])
411             .expect("failed to write mutated value to file");
412     }
413 
414     #[test]
load_elf32()415     fn load_elf32() {
416         let gm = create_guest_mem();
417         let kernel_addr = GuestAddress(0x0);
418         let mut image = make_elf32_bin();
419         let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap();
420         assert_eq!(kernel.address_range.start, 0);
421         assert_eq!(kernel.address_range.end, 0xa_2038);
422         assert_eq!(kernel.size, 0xa_2038);
423         assert_eq!(kernel.entry, GuestAddress(0x3dc0));
424     }
425 
426     #[test]
load_elf64()427     fn load_elf64() {
428         let gm = create_guest_mem();
429         let kernel_addr = GuestAddress(0x0);
430         let mut image = make_elf64_bin();
431         let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF");
432         assert_eq!(kernel.address_range.start, 0x20_0000);
433         assert_eq!(kernel.address_range.end, 0x20_0035);
434         assert_eq!(kernel.size, 0x35);
435         assert_eq!(kernel.entry, GuestAddress(0x20_000e));
436     }
437 
438     #[test]
bad_magic()439     fn bad_magic() {
440         let gm = create_guest_mem();
441         let kernel_addr = GuestAddress(0x0);
442         let mut bad_image = make_elf64_bin();
443         mutate_elf_bin(&bad_image, 0x1, 0x33);
444         assert_eq!(
445             Err(Error::InvalidMagicNumber),
446             load_elf(&gm, kernel_addr, &mut bad_image, 0)
447         );
448     }
449 
450     #[test]
bad_endian()451     fn bad_endian() {
452         // Only little endian is supported
453         let gm = create_guest_mem();
454         let kernel_addr = GuestAddress(0x20_0000);
455         let mut bad_image = make_elf64_bin();
456         mutate_elf_bin(&bad_image, 0x5, 2);
457         assert_eq!(
458             Err(Error::BigEndianOnLittle),
459             load_elf(&gm, kernel_addr, &mut bad_image, 0)
460         );
461     }
462 
463     #[test]
bad_phoff()464     fn bad_phoff() {
465         // program header has to be past the end of the elf header
466         let gm = create_guest_mem();
467         let kernel_addr = GuestAddress(0x0);
468         let mut bad_image = make_elf64_bin();
469         mutate_elf_bin(&bad_image, 0x20, 0x10);
470         assert_eq!(
471             Err(Error::InvalidProgramHeaderOffset),
472             load_elf(&gm, kernel_addr, &mut bad_image, 0)
473         );
474     }
475 
476     #[test]
paddr_below_start()477     fn paddr_below_start() {
478         let gm = create_guest_mem();
479         // test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address
480         let kernel_addr = GuestAddress(0x30_0000);
481         let mut image = make_elf64_bin();
482         let res = load_elf(&gm, kernel_addr, &mut image, 0);
483         assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange));
484     }
485 }
486