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