loader/
elf.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4// Portions Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5// SPDX-License-Identifier: Apache-2.0
6//
7// Portions Copyright 2017 The Chromium OS Authors. All rights reserved.
8// Use of this source code is governed by a BSD-style license that can be
9// found in the THIRD-PARTY file.
10
11//! Helper for loading an ELF kernel image.
12
13use crate::importer::GuestArch;
14use crate::importer::GuestArchKind;
15use crate::importer::ImageLoad;
16use hvdef::HV_PAGE_SIZE;
17use object::ReadCache;
18use object::ReadRef;
19use object::elf;
20use object::read::elf::FileHeader;
21use std::io::Read;
22use std::io::Seek;
23use thiserror::Error;
24
25type LE = object::LittleEndian;
26const LE: LE = LE {};
27
28#[derive(Debug, Error)]
29pub enum Error {
30    #[error("failed to read file header")]
31    ReadFileHeader,
32    #[error("invalid file header")]
33    InvalidFileHeader,
34    #[error("target machine mismatch")]
35    TargetMachineMismatch,
36    #[error("unsupported ELF file byte order")]
37    BigEndianElfOnLittle,
38    #[error(
39        "invalid entry address found in ELF header: {e_entry:#x}, start address: {start_address:#x}, load offset: {load_offset:#x}"
40    )]
41    InvalidEntryAddress {
42        e_entry: u64,
43        start_address: u64,
44        load_offset: u64,
45    },
46    #[error("failed to parse ELF program header")]
47    InvalidProgramHeader(#[source] object::read::Error),
48    #[error("adding load offset {load_offset} to paddr {p_paddr} overflowed")]
49    LoadOffsetOverflow { load_offset: u64, p_paddr: u64 },
50    #[error("invalid ELF program header memory offset {mem_offset}, below start {start_address}")]
51    InvalidProgramHeaderMemoryOffset { mem_offset: u64, start_address: u64 },
52    #[error(
53        "adding reloc bias {reloc_bias} and load offset {load_offset} to paddr {p_paddr} overflowed"
54    )]
55    RelocBiasOverflow {
56        load_offset: u64,
57        reloc_bias: u64,
58        p_paddr: u64,
59    },
60    #[error("failed to read kernel image")]
61    ReadKernelImage,
62    #[error("failed during import pages call")]
63    ImportPages(#[source] anyhow::Error),
64    #[error("failed to seek to offset of kernel image")]
65    SeekKernelImage,
66}
67
68pub type Result<T> = std::result::Result<T, Error>;
69
70/// Information about the loaded ELF image.
71#[derive(Debug)]
72pub struct LoadInfo {
73    /// The minimum physical address used when loading the ELF image. This may be different from the start_address
74    /// provided, as the ELF image controls where it should be loaded.
75    pub minimum_address_used: u64,
76    /// The next available physical address after the kernel was loaded.
77    pub next_available_address: u64,
78    /// The entrypoint of the image.
79    pub entrypoint: u64,
80}
81
82/// Loads a kernel from a vmlinux elf image to a slice
83///
84/// # Arguments
85///
86/// * `guest_mem` - The guest memory region the kernel is written to.
87/// * `kernel_image` - Input vmlinux image.
88/// * `start_address` - For x86_64, this is the start of the high memory. Kernel should reside above it.
89/// * `load_offset` - The offset to add to each loaded address.
90/// * `assume_pic` - Assume that the image contains Position-Independent Code.
91/// * `acceptance` - The page acceptance type for pages in the kernel.
92/// * `tag` - The tag used to report igvm imports.
93///
94/// Returns (minimum offset written, maximum offset written, entry address of the kernel).
95pub fn load_static_elf<F, R: GuestArch>(
96    importer: &mut dyn ImageLoad<R>,
97    kernel_image: &mut F,
98    start_address: u64,
99    load_offset: u64,
100    assume_pic: bool,
101    acceptance: crate::importer::BootPageAcceptance,
102    tag: &str,
103) -> Result<LoadInfo>
104where
105    F: Read + Seek,
106{
107    let reader = ReadCache::new(kernel_image);
108    let ehdr: &elf::FileHeader64<LE> = reader.read_at(0).map_err(|_| Error::ReadFileHeader)?;
109
110    // Sanity checks
111    if !ehdr.is_supported() {
112        return Err(Error::InvalidFileHeader);
113    }
114    if ehdr.is_big_endian() {
115        return Err(Error::BigEndianElfOnLittle);
116    }
117
118    match R::arch() {
119        GuestArchKind::Aarch64 => {
120            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_AARCH64) {
121                tracing::error!(
122                    "ELF file target machine mismatch, was the file built for aarch64?"
123                );
124                return Err(Error::TargetMachineMismatch);
125            }
126        }
127        GuestArchKind::X86_64 => {
128            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_X86_64) {
129                tracing::error!("ELF file target machine mismatch, was the file built for X86_64?");
130                return Err(Error::TargetMachineMismatch);
131            }
132        }
133    }
134
135    let e_entry = ehdr.e_entry.get(LE);
136    let load_offset = if assume_pic && e_entry < start_address {
137        // The kernel is assumed to contain PIC
138        start_address + load_offset
139    } else {
140        load_offset
141    };
142
143    let entry = e_entry
144        .checked_add(load_offset)
145        .ok_or(Error::InvalidEntryAddress {
146            e_entry,
147            start_address,
148            load_offset,
149        })?;
150    if entry < start_address {
151        return Err(Error::InvalidEntryAddress {
152            e_entry,
153            start_address,
154            load_offset,
155        });
156    }
157
158    let phdrs = ehdr
159        .program_headers(LE, &reader)
160        .map_err(Error::InvalidProgramHeader)?;
161
162    // The first pass on the sections provides the layout data
163    let (lowest_addr, last_offset, reloc_bias) = {
164        let mut lowest_addr = u64::MAX;
165        let mut last_offset = load_offset;
166
167        // Read in each section pointed to by the program headers.
168        for phdr in phdrs {
169            if phdr.p_type.get(LE) != elf::PT_LOAD {
170                continue;
171            }
172
173            let p_paddr = phdr.p_paddr.get(LE);
174            let mem_offset = p_paddr
175                .checked_add(load_offset)
176                .ok_or(Error::LoadOffsetOverflow {
177                    load_offset,
178                    p_paddr,
179                })?;
180
181            if mem_offset < start_address {
182                return Err(Error::InvalidProgramHeaderMemoryOffset {
183                    mem_offset,
184                    start_address,
185                });
186            }
187
188            let page_mask = HV_PAGE_SIZE - 1;
189            let page_base = mem_offset / HV_PAGE_SIZE;
190            let page_count: u64 =
191                ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
192
193            lowest_addr = lowest_addr.min(page_base * HV_PAGE_SIZE);
194            last_offset = last_offset.max((page_base + page_count) * HV_PAGE_SIZE);
195        }
196
197        (
198            lowest_addr,
199            last_offset,
200            if assume_pic {
201                lowest_addr - start_address
202            } else {
203                0
204            },
205        )
206    };
207
208    // During the second pass, read in each section pointed to by the program headers,
209    // and import into the guest memory.
210    for phdr in phdrs {
211        if phdr.p_type.get(LE) != elf::PT_LOAD {
212            continue;
213        }
214
215        let p_paddr = phdr.p_paddr.get(LE);
216        let mem_offset = p_paddr
217            .checked_add(load_offset)
218            .ok_or(Error::LoadOffsetOverflow {
219                load_offset,
220                p_paddr,
221            })?
222            .checked_sub(reloc_bias)
223            .ok_or(Error::RelocBiasOverflow {
224                load_offset,
225                reloc_bias,
226                p_paddr,
227            })?;
228
229        if mem_offset < start_address {
230            return Err(Error::InvalidProgramHeaderMemoryOffset {
231                mem_offset,
232                start_address,
233            });
234        }
235
236        let page_mask = HV_PAGE_SIZE - 1;
237
238        let filesz = phdr.p_filesz.get(LE);
239        let mut v = vec![0; ((mem_offset & page_mask) + filesz) as usize];
240        if filesz != 0 {
241            let v_start_offset = (mem_offset & page_mask) as usize;
242            let read_length = (v.len() - v_start_offset) as u64;
243            v[v_start_offset..].copy_from_slice(
244                reader
245                    .read_bytes_at(phdr.p_offset.get(LE), read_length)
246                    .map_err(|_| Error::ReadKernelImage)?,
247            );
248        }
249
250        let page_base = mem_offset / HV_PAGE_SIZE;
251        let page_count =
252            ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
253        importer
254            .import_pages(page_base, page_count, tag, acceptance, &v)
255            .map_err(Error::ImportPages)?;
256    }
257
258    Ok(LoadInfo {
259        minimum_address_used: lowest_addr - reloc_bias,
260        next_available_address: last_offset - reloc_bias,
261        entrypoint: entry - reloc_bias,
262    })
263}