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