kernel_loader/
lib.rs

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
7use std::mem;
8
9use base::FileReadWriteAtVolatile;
10use base::VolatileSlice;
11use remain::sorted;
12use resources::AddressRange;
13use thiserror::Error;
14use vm_memory::GuestAddress;
15use vm_memory::GuestMemory;
16use zerocopy::FromBytes;
17use zerocopy::IntoBytes;
18
19mod 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)]
26mod elf;
27
28mod arm64;
29
30pub use arm64::load_arm64_kernel;
31pub use arm64::load_arm64_kernel_lz4;
32pub use multiboot::load_multiboot;
33pub use multiboot::multiboot_header_from_file;
34
35#[sorted]
36#[derive(Error, Debug, PartialEq, Eq)]
37pub 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}
79pub 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.
83pub 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    /// ELF class or equivalent for other image formats.
96    pub class: ElfClass,
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq)]
100/// ELF image class (32- or 64-bit ELF)
101pub enum ElfClass {
102    ElfClass32,
103    ElfClass64,
104}
105
106/// Loads a kernel from a 32-bit or 64-bit ELF image into memory.
107///
108/// The ELF file will be loaded at the physical address specified by the `p_paddr` fields of its
109/// program headers.
110///
111/// # Arguments
112///
113/// * `guest_mem` - The guest memory region the kernel is written to.
114/// * `kernel_start` - The minimum guest address to allow when loading program headers.
115/// * `kernel_image` - Input vmlinux image.
116/// * `phys_offset` - An offset in bytes to add to each physical address (`p_paddr`).
117pub fn load_elf<F>(
118    guest_mem: &GuestMemory,
119    kernel_start: GuestAddress,
120    kernel_image: &mut F,
121    phys_offset: u64,
122) -> Result<LoadedKernel>
123where
124    F: FileReadWriteAtVolatile,
125{
126    let (elf, class) = read_elf(kernel_image)?;
127    let mut start = None;
128    let mut end = 0;
129
130    // Read in each section pointed to by the program headers.
131    for phdr in &elf.program_headers {
132        if phdr.p_type != elf::PT_LOAD {
133            continue;
134        }
135
136        let paddr = phdr
137            .p_paddr
138            .checked_add(phys_offset)
139            .ok_or(Error::ProgramHeaderAddressOutOfRange)?;
140
141        if paddr < kernel_start.offset() {
142            return Err(Error::ProgramHeaderAddressOutOfRange);
143        }
144
145        if start.is_none() {
146            start = Some(paddr);
147        }
148
149        end = paddr
150            .checked_add(phdr.p_memsz)
151            .ok_or(Error::InvalidProgramHeaderMemSize)?;
152
153        if phdr.p_filesz == 0 {
154            continue;
155        }
156
157        let guest_slice = guest_mem
158            .get_slice_at_addr(GuestAddress(paddr), phdr.p_filesz as usize)
159            .map_err(|_| Error::ReadKernelImage)?;
160        kernel_image
161            .read_exact_at_volatile(guest_slice, phdr.p_offset)
162            .map_err(|_| Error::ReadKernelImage)?;
163    }
164
165    // We should have found at least one PT_LOAD program header. If not, `start` will not be set.
166    let start = start.ok_or(Error::NoLoadableProgramHeaders)?;
167
168    let size = end
169        .checked_sub(start)
170        .ok_or(Error::InvalidProgramHeaderSize)?;
171
172    let address_range =
173        AddressRange::from_start_and_size(start, size).ok_or(Error::InvalidProgramHeaderSize)?;
174
175    // The entry point address must fall within one of the loaded sections.
176    // We approximate this by checking whether it within the bounds of the first and last sections.
177    let entry = elf
178        .file_header
179        .e_entry
180        .checked_add(phys_offset)
181        .ok_or(Error::InvalidEntryPoint)?;
182    if !address_range.contains(entry) {
183        return Err(Error::InvalidEntryPoint);
184    }
185
186    Ok(LoadedKernel {
187        address_range,
188        size,
189        entry: GuestAddress(entry),
190        class,
191    })
192}
193
194struct Elf64 {
195    file_header: elf::Elf64_Ehdr,
196    program_headers: Vec<elf::Elf64_Phdr>,
197}
198
199/// Reads the headers of an ELF32 or ELF64 object file.  Returns ELF file and program headers,
200/// converted to ELF64 format as a single internal representation that can handle either 32- or
201/// 64-bit images, and the ELF class of the original image.
202fn read_elf<F>(file: &mut F) -> Result<(Elf64, ElfClass)>
203where
204    F: FileReadWriteAtVolatile,
205{
206    // Read the ELF identification (e_ident) block.
207    let mut ident = [0u8; 16];
208    file.read_exact_at_volatile(VolatileSlice::new(&mut ident), 0)
209        .map_err(|_| Error::ReadHeader)?;
210
211    // e_ident checks
212    if ident[elf::EI_MAG0 as usize] != elf::ELFMAG0 as u8
213        || ident[elf::EI_MAG1 as usize] != elf::ELFMAG1
214        || ident[elf::EI_MAG2 as usize] != elf::ELFMAG2
215        || ident[elf::EI_MAG3 as usize] != elf::ELFMAG3
216    {
217        return Err(Error::InvalidMagicNumber);
218    }
219    if ident[elf::EI_DATA as usize] != elf::ELFDATA2LSB as u8 {
220        return Err(Error::BigEndianOnLittle);
221    }
222    if ident[elf::EI_VERSION as usize] != elf::EV_CURRENT as u8 {
223        return Err(Error::InvalidElfVersion);
224    }
225
226    let ei_class = ident[elf::EI_CLASS as usize] as u32;
227    match ei_class {
228        elf::ELFCLASS32 => Ok((
229            read_elf_by_type::<_, elf::Elf32_Ehdr, elf::Elf32_Phdr>(file)?,
230            ElfClass::ElfClass32,
231        )),
232        elf::ELFCLASS64 => Ok((
233            read_elf_by_type::<_, elf::Elf64_Ehdr, elf::Elf64_Phdr>(file)?,
234            ElfClass::ElfClass64,
235        )),
236        _ => Err(Error::InvalidElfClass),
237    }
238}
239
240/// Reads the headers of an ELF32 or ELF64 object file.  Returns ELF file and program headers,
241/// converted to ELF64 format.  `FileHeader` and `ProgramHeader` are the ELF32 or ELF64 ehdr/phdr
242/// types to read from the file.  Caller should check that `file` is a valid ELF file before calling
243/// this function.
244fn read_elf_by_type<F, FileHeader, ProgramHeader>(file: &mut F) -> Result<Elf64>
245where
246    F: FileReadWriteAtVolatile,
247    FileHeader: IntoBytes + FromBytes + Default + Into<elf::Elf64_Ehdr>,
248    ProgramHeader: IntoBytes + FromBytes + Clone + Default + Into<elf::Elf64_Phdr>,
249{
250    let mut ehdr = FileHeader::new_zeroed();
251    file.read_exact_at_volatile(VolatileSlice::new(ehdr.as_mut_bytes()), 0)
252        .map_err(|_| Error::ReadHeader)?;
253    let ehdr: elf::Elf64_Ehdr = ehdr.into();
254
255    if ehdr.e_phentsize as usize != mem::size_of::<ProgramHeader>() {
256        return Err(Error::InvalidProgramHeaderSize);
257    }
258    if (ehdr.e_phoff as usize) < mem::size_of::<FileHeader>() {
259        // If the program header is backwards, bail.
260        return Err(Error::InvalidProgramHeaderOffset);
261    }
262
263    let num_phdrs = ehdr.e_phnum as usize;
264    let mut phdrs = vec![ProgramHeader::default(); num_phdrs];
265    file.read_exact_at_volatile(VolatileSlice::new(phdrs.as_mut_bytes()), ehdr.e_phoff)
266        .map_err(|_| Error::ReadProgramHeader)?;
267
268    Ok(Elf64 {
269        file_header: ehdr,
270        program_headers: phdrs.into_iter().map(|ph| ph.into()).collect(),
271    })
272}
273
274impl From<elf::Elf32_Ehdr> for elf::Elf64_Ehdr {
275    fn from(ehdr32: elf::Elf32_Ehdr) -> Self {
276        elf::Elf64_Ehdr {
277            e_ident: ehdr32.e_ident,
278            e_type: ehdr32.e_type as elf::Elf64_Half,
279            e_machine: ehdr32.e_machine as elf::Elf64_Half,
280            e_version: ehdr32.e_version as elf::Elf64_Word,
281            e_entry: ehdr32.e_entry as elf::Elf64_Addr,
282            e_phoff: ehdr32.e_phoff as elf::Elf64_Off,
283            e_shoff: ehdr32.e_shoff as elf::Elf64_Off,
284            e_flags: ehdr32.e_flags as elf::Elf64_Word,
285            e_ehsize: ehdr32.e_ehsize as elf::Elf64_Half,
286            e_phentsize: ehdr32.e_phentsize as elf::Elf64_Half,
287            e_phnum: ehdr32.e_phnum as elf::Elf64_Half,
288            e_shentsize: ehdr32.e_shentsize as elf::Elf64_Half,
289            e_shnum: ehdr32.e_shnum as elf::Elf64_Half,
290            e_shstrndx: ehdr32.e_shstrndx as elf::Elf64_Half,
291        }
292    }
293}
294
295impl From<elf::Elf32_Phdr> for elf::Elf64_Phdr {
296    fn from(phdr32: elf::Elf32_Phdr) -> Self {
297        elf::Elf64_Phdr {
298            p_type: phdr32.p_type as elf::Elf64_Word,
299            p_flags: phdr32.p_flags as elf::Elf64_Word,
300            p_offset: phdr32.p_offset as elf::Elf64_Off,
301            p_vaddr: phdr32.p_vaddr as elf::Elf64_Addr,
302            p_paddr: phdr32.p_paddr as elf::Elf64_Addr,
303            p_filesz: phdr32.p_filesz as elf::Elf64_Xword,
304            p_memsz: phdr32.p_memsz as elf::Elf64_Xword,
305            p_align: phdr32.p_align as elf::Elf64_Xword,
306        }
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use std::fs::File;
313    use std::io::Seek;
314    use std::io::SeekFrom;
315    use std::io::Write;
316
317    use tempfile::tempfile;
318    use vm_memory::GuestAddress;
319    use vm_memory::GuestMemory;
320
321    use super::*;
322
323    const MEM_SIZE: u64 = 0x40_0000;
324
325    fn create_guest_mem() -> GuestMemory {
326        GuestMemory::new(&[(GuestAddress(0x0), MEM_SIZE)]).unwrap()
327    }
328
329    // Elf32 image that prints hello world on x86.
330    fn make_elf32_bin() -> File {
331        // test_elf32.bin built on Linux with gcc -m32 -static-pie
332        let bytes = include_bytes!("test_elf32.bin");
333        make_elf_bin(bytes)
334    }
335
336    // Elf64 image that prints hello world on x86_64.
337    fn make_elf64_bin() -> File {
338        let bytes = include_bytes!("test_elf64.bin");
339        make_elf_bin(bytes)
340    }
341
342    fn make_elf_bin(elf_bytes: &[u8]) -> File {
343        let mut file = tempfile().expect("failed to create tempfile");
344        file.write_all(elf_bytes)
345            .expect("failed to write elf to shared memory");
346        file
347    }
348
349    fn mutate_elf_bin(mut f: &File, offset: u64, val: u8) {
350        f.seek(SeekFrom::Start(offset))
351            .expect("failed to seek file");
352        f.write_all(&[val])
353            .expect("failed to write mutated value to file");
354    }
355
356    #[test]
357    fn load_elf32() {
358        let gm = create_guest_mem();
359        let kernel_addr = GuestAddress(0x0);
360        let mut image = make_elf32_bin();
361        let kernel = load_elf(&gm, kernel_addr, &mut image, 0).unwrap();
362        assert_eq!(kernel.address_range.start, 0x20_0000);
363        assert_eq!(kernel.address_range.end, 0x20_002e);
364        assert_eq!(kernel.size, 0x2f);
365        assert_eq!(kernel.entry, GuestAddress(0x20_000e));
366        assert_eq!(kernel.class, ElfClass::ElfClass32);
367    }
368
369    #[test]
370    fn load_elf64() {
371        let gm = create_guest_mem();
372        let kernel_addr = GuestAddress(0x0);
373        let mut image = make_elf64_bin();
374        let kernel = load_elf(&gm, kernel_addr, &mut image, 0).expect("failed to load ELF");
375        assert_eq!(kernel.address_range.start, 0x20_0000);
376        assert_eq!(kernel.address_range.end, 0x20_002f);
377        assert_eq!(kernel.size, 0x30);
378        assert_eq!(kernel.entry, GuestAddress(0x20_000e));
379        assert_eq!(kernel.class, ElfClass::ElfClass64);
380    }
381
382    #[test]
383    fn bad_magic() {
384        let gm = create_guest_mem();
385        let kernel_addr = GuestAddress(0x0);
386        let mut bad_image = make_elf64_bin();
387        mutate_elf_bin(&bad_image, 0x1, 0x33);
388        assert_eq!(
389            Err(Error::InvalidMagicNumber),
390            load_elf(&gm, kernel_addr, &mut bad_image, 0)
391        );
392    }
393
394    #[test]
395    fn bad_endian() {
396        // Only little endian is supported
397        let gm = create_guest_mem();
398        let kernel_addr = GuestAddress(0x20_0000);
399        let mut bad_image = make_elf64_bin();
400        mutate_elf_bin(&bad_image, 0x5, 2);
401        assert_eq!(
402            Err(Error::BigEndianOnLittle),
403            load_elf(&gm, kernel_addr, &mut bad_image, 0)
404        );
405    }
406
407    #[test]
408    fn bad_phoff() {
409        // program header has to be past the end of the elf header
410        let gm = create_guest_mem();
411        let kernel_addr = GuestAddress(0x0);
412        let mut bad_image = make_elf64_bin();
413        mutate_elf_bin(&bad_image, 0x20, 0x10);
414        assert_eq!(
415            Err(Error::InvalidProgramHeaderOffset),
416            load_elf(&gm, kernel_addr, &mut bad_image, 0)
417        );
418    }
419
420    #[test]
421    fn paddr_below_start() {
422        let gm = create_guest_mem();
423        // test_elf.bin loads a phdr at 0x20_0000, so this will fail due to an out-of-bounds address
424        let kernel_addr = GuestAddress(0x30_0000);
425        let mut image = make_elf64_bin();
426        let res = load_elf(&gm, kernel_addr, &mut image, 0);
427        assert_eq!(res, Err(Error::ProgramHeaderAddressOutOfRange));
428    }
429}