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::io::Read;
9 use std::io::Seek;
10 use std::io::SeekFrom;
11 use std::mem;
12
13 use base::AsRawDescriptor;
14 use data_model::zerocopy_from_reader;
15 use remain::sorted;
16 use resources::AddressRange;
17 use thiserror::Error;
18 use vm_memory::GuestAddress;
19 use vm_memory::GuestMemory;
20 use zerocopy::FromBytes;
21
22 #[allow(dead_code)]
23 #[allow(non_camel_case_types)]
24 #[allow(non_snake_case)]
25 #[allow(non_upper_case_globals)]
26 #[allow(clippy::all)]
27 mod elf;
28
29 mod arm64;
30
31 pub use arm64::load_arm64_kernel;
32
33 #[sorted]
34 #[derive(Error, Debug, PartialEq, Eq)]
35 pub enum Error {
36 #[error("trying to load big-endian binary on little-endian machine")]
37 BigEndianOnLittle,
38 #[error("failed writing command line to guest memory")]
39 CommandLineCopy,
40 #[error("command line overflowed guest memory")]
41 CommandLineOverflow,
42 #[error("invalid elf class")]
43 InvalidElfClass,
44 #[error("invalid elf version")]
45 InvalidElfVersion,
46 #[error("invalid entry point")]
47 InvalidEntryPoint,
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: Read + Seek + AsRawDescriptor,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: Read + Seek + AsRawDescriptor,
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: Read + Seek + AsRawDescriptor,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: Read + Seek + AsRawDescriptor,
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: Read + Seek + AsRawDescriptor,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: Read + Seek + AsRawDescriptor,
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: Read + Seek + AsRawDescriptor,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: Read + Seek + AsRawDescriptor,
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 kernel_image
219 .seek(SeekFrom::Start(phdr.p_offset))
220 .map_err(|_| Error::SeekKernelStart)?;
221
222 guest_mem
223 .read_to_memory(GuestAddress(paddr), kernel_image, phdr.p_filesz as usize)
224 .map_err(|_| Error::ReadKernelImage)?;
225 }
226
227 // We should have found at least one PT_LOAD program header. If not, `start` will not be set.
228 let start = start.ok_or(Error::NoLoadableProgramHeaders)?;
229
230 let size = end
231 .checked_sub(start)
232 .ok_or(Error::InvalidProgramHeaderSize)?;
233
234 let address_range = AddressRange { start, end };
235
236 // The entry point address must fall within one of the loaded sections.
237 // We approximate this by checking whether it within the bounds of the first and last sections.
238 let entry = elf
239 .file_header
240 .e_entry
241 .checked_add(phys_offset)
242 .ok_or(Error::InvalidEntryPoint)?;
243 if !address_range.contains(entry) {
244 return Err(Error::InvalidEntryPoint);
245 }
246
247 Ok(LoadedKernel {
248 address_range,
249 size,
250 entry: GuestAddress(entry),
251 })
252 }
253
254 /// Writes the command line string to the given memory slice.
255 ///
256 /// # Arguments
257 ///
258 /// * `guest_mem` - A u8 slice that will be partially overwritten by the command line.
259 /// * `guest_addr` - The address in `guest_mem` at which to load the command line.
260 /// * `cmdline` - The kernel command line.
load_cmdline( guest_mem: &GuestMemory, guest_addr: GuestAddress, cmdline: &CStr, ) -> Result<()>261 pub fn load_cmdline(
262 guest_mem: &GuestMemory,
263 guest_addr: GuestAddress,
264 cmdline: &CStr,
265 ) -> Result<()> {
266 let len = cmdline.to_bytes().len();
267 if len == 0 {
268 return Ok(());
269 }
270
271 let end = guest_addr
272 .checked_add(len as u64 + 1)
273 .ok_or(Error::CommandLineOverflow)?; // Extra for null termination.
274 if end > guest_mem.end_addr() {
275 return Err(Error::CommandLineOverflow);
276 }
277
278 guest_mem
279 .write_at_addr(cmdline.to_bytes_with_nul(), guest_addr)
280 .map_err(|_| Error::CommandLineCopy)?;
281
282 Ok(())
283 }
284
285 struct Elf64 {
286 file_header: elf::Elf64_Ehdr,
287 program_headers: Vec<elf::Elf64_Phdr>,
288 }
289
290 /// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
291 /// converted to ELF64 format. If `required_ei_class` is Some and the file's ELF ei_class doesn't
292 /// match, an Err is returned.
read_elf<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64> where F: Read + Seek + AsRawDescriptor,293 fn read_elf<F>(file: &mut F, required_ei_class: Option<u32>) -> Result<Elf64>
294 where
295 F: Read + Seek + AsRawDescriptor,
296 {
297 // Read the ELF identification (e_ident) block.
298 file.seek(SeekFrom::Start(0))
299 .map_err(|_| Error::SeekKernelStart)?;
300 let mut ident = [0u8; 16];
301 file.read_exact(&mut ident).map_err(|_| Error::ReadHeader)?;
302
303 // e_ident checks
304 if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
305 || ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
306 || ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
307 || ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
308 {
309 return Err(Error::InvalidMagicNumber);
310 }
311 if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
312 return Err(Error::BigEndianOnLittle);
313 }
314 if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 {
315 return Err(Error::InvalidElfVersion);
316 }
317
318 let ei_class = ident[elf::EI_CLASS as usize] as u32;
319 if let Some(required_ei_class) = required_ei_class {
320 if ei_class != required_ei_class {
321 return Err(Error::InvalidElfClass);
322 }
323 }
324 match ei_class {
325 elf::ELFCLASS32 => read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file),
326 elf::ELFCLASS64 => read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file),
327 _ => Err(Error::InvalidElfClass),
328 }
329 }
330
331 /// Reads the headers of an ELF32 or ELF64 object file. Returns ELF file and program headers,
332 /// converted to ELF64 format. `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr
333 /// types to read from the file. Caller should check that `file` is a valid ELF file before calling
334 /// this function.
read_elf_by_type<F, FileHeader, ProgramHeader>(mut file: &mut F) -> Result<Elf64> where F: Read + Seek + AsRawDescriptor, FileHeader: FromBytes + Default + Into<elf::Elf64_Ehdr>, ProgramHeader: FromBytes + Default + Into<elf::Elf64_Phdr>,335 fn read_elf_by_type<F, FileHeader, ProgramHeader>(mut file: &mut F) -> Result<Elf64>
336 where
337 F: Read + Seek + AsRawDescriptor,
338 FileHeader: FromBytes + Default + Into<elf::Elf64_Ehdr>,
339 ProgramHeader: FromBytes + Default + Into<elf::Elf64_Phdr>,
340 {
341 file.seek(SeekFrom::Start(0))
342 .map_err(|_| Error::SeekKernelStart)?;
343 let ehdr: FileHeader = zerocopy_from_reader(&mut file).map_err(|_| Error::ReadHeader)?;
344 let ehdr: elf::Elf64_Ehdr = ehdr.into();
345
346 if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() {
347 return Err(Error::InvalidProgramHeaderSize);
348 }
349 if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() {
350 // If the program header is backwards, bail.
351 return Err(Error::InvalidProgramHeaderOffset);
352 }
353
354 file.seek(SeekFrom::Start(ehdr.e_phoff as u64))
355 .map_err(|_| Error::SeekProgramHeader)?;
356 let phdrs: Vec<ProgramHeader> = (0..ehdr.e_phnum)
357 .enumerate()
358 .map(|_| zerocopy_from_reader(&mut file).map_err(|_| Error::ReadProgramHeader))
359 .collect::<Result<Vec<ProgramHeader>>>()?;
360
361 Ok(Elf64 {
362 file_header: ehdr,
363 program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(),
364 })
365 }
366
367 impl From<elf::Elf32_Ehdr> for elf::Elf64_Ehdr {
from(ehdr32: elf::Elf32_Ehdr) -> Self368 fn from(ehdr32: elf::Elf32_Ehdr) -> Self {
369 elf::Elf64_Ehdr {
370 e_ident: ehdr32.e_ident,
371 e_type: ehdr32.e_type as elf::Elf64_Half,
372 e_machine: ehdr32.e_machine as elf::Elf64_Half,
373 e_version: ehdr32.e_version as elf::Elf64_Word,
374 e_entry: ehdr32.e_entry as elf::Elf64_Addr,
375 e_phoff: ehdr32.e_phoff as elf::Elf64_Off,
376 e_shoff: ehdr32.e_shoff as elf::Elf64_Off,
377 e_flags: ehdr32.e_flags as elf::Elf64_Word,
378 e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half,
379 e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half,
380 e_phnum: ehdr32.e_phnum as elf::Elf64_Half,
381 e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half,
382 e_shnum: ehdr32.e_shnum as elf::Elf64_Half,
383 e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half,
384 }
385 }
386 }
387
388 impl From<elf::Elf32_Phdr> for elf::Elf64_Phdr {
from(phdr32: elf::Elf32_Phdr) -> Self389 fn from(phdr32: elf::Elf32_Phdr) -> Self {
390 elf::Elf64_Phdr {
391 p_type: phdr32.p_type as elf::Elf64_Word,
392 p_flags: phdr32.p_flags as elf::Elf64_Word,
393 p_offset: phdr32.p_offset as elf::Elf64_Off,
394 p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr,
395 p_paddr: phdr32.p_paddr as elf::Elf64_Addr,
396 p_filesz: phdr32.p_filesz as elf::Elf64_Xword,
397 p_memsz: phdr32.p_memsz as elf::Elf64_Xword,
398 p_align: phdr32.p_align as elf::Elf64_Xword,
399 }
400 }
401 }
402
403 #[cfg(test)]
404 mod test {
405 use std::fs::File;
406 use std::io::Write;
407
408 use tempfile::tempfile;
409 use vm_memory::GuestAddress;
410 use vm_memory::GuestMemory;
411
412 use super::*;
413
414 const MEM_SIZE: u64 = 0x40_0000;
415
create_guest_mem() -> GuestMemory416 fn create_guest_mem() -> GuestMemory {
417 GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
418 }
419
420 #[test]
cmdline_overflow()421 fn cmdline_overflow() {
422 let gm = create_guest_mem();
423 let cmdline_address = GuestAddress(MEM_SIZE - 5);
424 assert_eq!(
425 Err(Error::CommandLineOverflow),
426 load_cmdline(
427 &gm,
428 cmdline_address,
429 CStr::from_bytes_with_nul(b"12345\0").unwrap()
430 )
431 );
432 }
433
434 #[test]
cmdline_write_end()435 fn cmdline_write_end() {
436 let gm = create_guest_mem();
437 let mut cmdline_address = GuestAddress(45);
438 assert_eq!(
439 Ok(()),
440 load_cmdline(
441 &gm,
442 cmdline_address,
443 CStr::from_bytes_with_nul(b"1234\0").unwrap()
444 )
445 );
446 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
447 assert_eq!(val, b'1');
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'2');
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'3');
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'4');
457 cmdline_address = cmdline_address.unchecked_add(1);
458 let val: u8 = gm.read_obj_from_addr(cmdline_address).unwrap();
459 assert_eq!(val, b'\0');
460 }
461
462 // Elf32 image that prints hello world on x86.
make_elf32_bin() -> File463 fn make_elf32_bin() -> File {
464 // test_elf32.bin built on Linux with gcc -m32 -static-pie
465 let bytes = include_bytes!("test_elf32.bin");
466 make_elf_bin(bytes)
467 }
468
469 // Elf64 image that prints hello world on x86_64.
make_elf64_bin() -> File470 fn make_elf64_bin() -> File {
471 let bytes = include_bytes!("test_elf64.bin");
472 make_elf_bin(bytes)
473 }
474
make_elf_bin(elf_bytes: &[u8]) -> File475 fn make_elf_bin(elf_bytes: &[u8]) -> File {
476 let mut file = tempfile().expect("failed to create tempfile");
477 file.write_all(elf_bytes)
478 .expect("failed to write elf to shared memory");
479 file
480 }
481
mutate_elf_bin(mut f: &File, offset: u64, val: u8)482 fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
483 f.seek(SeekFrom::Start(offset))
484 .expect("failed to seek file");
485 f.write_all(&[val])
486 .expect("failed to write mutated value to file");
487 }
488
489 #[test]
load_elf32()490 fn load_elf32() {
491 let gm = create_guest_mem();
492 let kernel_addr = GuestAddress(0x0);
493 let mut image = make_elf32_bin();
494 let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap();
495 assert_eq!(kernel.address_range.start, 0);
496 assert_eq!(kernel.address_range.end, 0xa_2038);
497 assert_eq!(kernel.size, 0xa_2038);
498 assert_eq!(kernel.entry, GuestAddress(0x3dc0));
499 }
500
501 #[test]
load_elf64()502 fn load_elf64() {
503 let gm = create_guest_mem();
504 let kernel_addr = GuestAddress(0x0);
505 let mut image = make_elf64_bin();
506 let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF");
507 assert_eq!(kernel.address_range.start, 0x20_0000);
508 assert_eq!(kernel.address_range.end, 0x20_0035);
509 assert_eq!(kernel.size, 0x35);
510 assert_eq!(kernel.entry, GuestAddress(0x20_000e));
511 }
512
513 #[test]
bad_magic()514 fn bad_magic() {
515 let gm = create_guest_mem();
516 let kernel_addr = GuestAddress(0x0);
517 let mut bad_image = make_elf64_bin();
518 mutate_elf_bin(&bad_image, 0x1, 0x33);
519 assert_eq!(
520 Err(Error::InvalidMagicNumber),
521 load_elf(&gm, kernel_addr, &mut bad_image, 0)
522 );
523 }
524
525 #[test]
bad_endian()526 fn bad_endian() {
527 // Only little endian is supported
528 let gm = create_guest_mem();
529 let kernel_addr = GuestAddress(0x20_0000);
530 let mut bad_image = make_elf64_bin();
531 mutate_elf_bin(&bad_image, 0x5, 2);
532 assert_eq!(
533 Err(Error::BigEndianOnLittle),
534 load_elf(&gm, kernel_addr, &mut bad_image, 0)
535 );
536 }
537
538 #[test]
bad_phoff()539 fn bad_phoff() {
540 // program header has to be past the end of the elf header
541 let gm = create_guest_mem();
542 let kernel_addr = GuestAddress(0x0);
543 let mut bad_image = make_elf64_bin();
544 mutate_elf_bin(&bad_image, 0x20, 0x10);
545 assert_eq!(
546 Err(Error::InvalidProgramHeaderOffset),
547 load_elf(&gm, kernel_addr, &mut bad_image, 0)
548 );
549 }
550
551 #[test]
paddr_below_start()552 fn paddr_below_start() {
553 let gm = create_guest_mem();
554 // test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address
555 let kernel_addr = GuestAddress(0x30_0000);
556 let mut image = make_elf64_bin();
557 let res = load_elf(&gm, kernel_addr, &mut image, 0);
558 assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange));
559 }
560 }
561