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::common::ChunkBuf;
14use crate::common::ImportFileRegion;
15use crate::common::ImportFileRegionError;
16use crate::importer::GuestArch;
17use crate::importer::GuestArchKind;
18use crate::importer::ImageLoad;
19use hvdef::HV_PAGE_SIZE;
20use object::ReadCache;
21use object::ReadRef;
22use object::elf;
23use object::read::elf::FileHeader;
24use std::io::Read;
25use std::io::Seek;
26use thiserror::Error;
27
28type LE = object::LittleEndian;
29const LE: LE = LE {};
30
31#[derive(Debug, Error)]
32pub enum Error {
33    #[error("failed to read file header")]
34    ReadFileHeader,
35    #[error("invalid file header")]
36    InvalidFileHeader,
37    #[error("target machine mismatch")]
38    TargetMachineMismatch,
39    #[error("unsupported ELF file byte order")]
40    BigEndianElfOnLittle,
41    #[error(
42        "invalid entry address found in ELF header: {e_entry:#x}, start address: {start_address:#x}, load offset: {load_offset:#x}"
43    )]
44    InvalidEntryAddress {
45        e_entry: u64,
46        start_address: u64,
47        load_offset: u64,
48    },
49    #[error("failed to parse ELF program header")]
50    InvalidProgramHeader(#[source] object::read::Error),
51    #[error("adding load offset {load_offset} to paddr {p_paddr} overflowed")]
52    LoadOffsetOverflow { load_offset: u64, p_paddr: u64 },
53    #[error("invalid ELF program header memory offset {mem_offset}, below start {start_address}")]
54    InvalidProgramHeaderMemoryOffset { mem_offset: u64, start_address: u64 },
55    #[error(
56        "adding reloc bias {reloc_bias} and load offset {load_offset} to paddr {p_paddr} overflowed"
57    )]
58    RelocBiasOverflow {
59        load_offset: u64,
60        reloc_bias: u64,
61        p_paddr: u64,
62    },
63    #[error("failed to read kernel image")]
64    ReadKernelImage,
65    #[error("failed to import file region")]
66    ImportFileRegion(#[source] ImportFileRegionError),
67    #[error("failed to seek to offset of kernel image")]
68    SeekKernelImage,
69}
70
71pub type Result<T> = std::result::Result<T, Error>;
72
73/// Information about the loaded ELF image.
74#[derive(Debug)]
75pub struct LoadInfo {
76    /// The minimum physical address used when loading the ELF image. This may be different from the start_address
77    /// provided, as the ELF image controls where it should be loaded.
78    pub minimum_address_used: u64,
79    /// The next available physical address after the kernel was loaded.
80    pub next_available_address: u64,
81    /// The entrypoint of the image.
82    pub entrypoint: u64,
83}
84
85/// Loads a kernel from a vmlinux elf image to a slice
86///
87/// # Arguments
88///
89/// * `guest_mem` - The guest memory region the kernel is written to.
90/// * `kernel_image` - Input vmlinux image.
91/// * `start_address` - For x86_64, this is the start of the high memory. Kernel should reside above it.
92/// * `load_offset` - The offset to add to each loaded address.
93/// * `assume_pic` - Assume that the image contains Position-Independent Code.
94/// * `acceptance` - The page acceptance type for pages in the kernel.
95/// * `tag` - The tag used to report igvm imports.
96///
97/// Returns (minimum offset written, maximum offset written, entry address of the kernel).
98pub fn load_static_elf<F, R: GuestArch>(
99    importer: &mut dyn ImageLoad<R>,
100    kernel_image: &mut F,
101    start_address: u64,
102    load_offset: u64,
103    assume_pic: bool,
104    acceptance: crate::importer::BootPageAcceptance,
105    tag: &str,
106) -> Result<LoadInfo>
107where
108    F: Read + Seek,
109{
110    let reader = ReadCache::new(&mut *kernel_image);
111    let ehdr: &elf::FileHeader64<LE> = reader.read_at(0).map_err(|_| Error::ReadFileHeader)?;
112
113    // Sanity checks
114    if !ehdr.is_supported() {
115        return Err(Error::InvalidFileHeader);
116    }
117    if ehdr.is_big_endian() {
118        return Err(Error::BigEndianElfOnLittle);
119    }
120
121    match R::arch() {
122        GuestArchKind::Aarch64 => {
123            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_AARCH64) {
124                tracing::error!(
125                    "ELF file target machine mismatch, was the file built for aarch64?"
126                );
127                return Err(Error::TargetMachineMismatch);
128            }
129        }
130        GuestArchKind::X86_64 => {
131            if ehdr.e_machine != object::U16::new(object::LittleEndian, elf::EM_X86_64) {
132                tracing::error!("ELF file target machine mismatch, was the file built for X86_64?");
133                return Err(Error::TargetMachineMismatch);
134            }
135        }
136    }
137
138    let e_entry = ehdr.e_entry.get(LE);
139    let phdrs = ehdr
140        .program_headers(LE, &reader)
141        .map_err(Error::InvalidProgramHeader)?;
142
143    // For PIC kernels, calculate load offset by checking lowest paddr in program headers.
144    // If it is below start_address, relocate kernel upward. Handles both:
145    // - Old kernels (< v6.17): startup code is in .head.text at start of .text, low entry point,
146    //   and matches physical load address
147    // - New kernels (≥ v6.17): startup code is in .init.text (commit: "x86/boot: Move startup code out of __head section"),
148    //   high entry point but low physical load address
149    let load_offset = if assume_pic {
150        let mut lowest_paddr = u64::MAX;
151        for phdr in phdrs {
152            if phdr.p_type.get(LE) == elf::PT_LOAD {
153                let p_paddr = phdr.p_paddr.get(LE);
154                lowest_paddr = lowest_paddr.min(p_paddr);
155            }
156        }
157        if lowest_paddr < start_address {
158            start_address - lowest_paddr + load_offset
159        } else {
160            load_offset
161        }
162    } else {
163        load_offset
164    };
165
166    let entry = e_entry
167        .checked_add(load_offset)
168        .ok_or(Error::InvalidEntryAddress {
169            e_entry,
170            start_address,
171            load_offset,
172        })?;
173    if entry < start_address {
174        return Err(Error::InvalidEntryAddress {
175            e_entry,
176            start_address,
177            load_offset,
178        });
179    }
180
181    // The first pass on the sections provides the layout data and collects
182    // segment info for the import pass.
183    struct SegmentInfo {
184        p_offset: u64,
185        p_paddr: u64,
186        p_filesz: u64,
187        p_memsz: u64,
188    }
189    let mut segments = Vec::new();
190
191    let (lowest_addr, last_offset, reloc_bias) = {
192        let mut lowest_addr = u64::MAX;
193        let mut last_offset = load_offset;
194
195        // Read in each section pointed to by the program headers.
196        for phdr in phdrs {
197            if phdr.p_type.get(LE) != elf::PT_LOAD {
198                continue;
199            }
200
201            let p_paddr = phdr.p_paddr.get(LE);
202            let mem_offset = p_paddr
203                .checked_add(load_offset)
204                .ok_or(Error::LoadOffsetOverflow {
205                    load_offset,
206                    p_paddr,
207                })?;
208
209            if mem_offset < start_address {
210                return Err(Error::InvalidProgramHeaderMemoryOffset {
211                    mem_offset,
212                    start_address,
213                });
214            }
215
216            let page_mask = HV_PAGE_SIZE - 1;
217            let page_base = mem_offset / HV_PAGE_SIZE;
218            let page_count: u64 =
219                ((mem_offset & page_mask) + phdr.p_memsz.get(LE) + page_mask) / HV_PAGE_SIZE;
220
221            lowest_addr = lowest_addr.min(page_base * HV_PAGE_SIZE);
222            last_offset = last_offset.max((page_base + page_count) * HV_PAGE_SIZE);
223
224            segments.push(SegmentInfo {
225                p_offset: phdr.p_offset.get(LE),
226                p_paddr,
227                p_filesz: phdr.p_filesz.get(LE),
228                p_memsz: phdr.p_memsz.get(LE),
229            });
230        }
231
232        (
233            lowest_addr,
234            last_offset,
235            if assume_pic {
236                lowest_addr - start_address
237            } else {
238                0
239            },
240        )
241    };
242
243    // Drop the reader to release the borrow on kernel_image.
244    drop(reader);
245
246    // During the second pass, import each segment.
247    let mut buf = ChunkBuf::new();
248    for seg in &segments {
249        let mem_offset = seg
250            .p_paddr
251            .checked_add(load_offset)
252            .ok_or(Error::LoadOffsetOverflow {
253                load_offset,
254                p_paddr: seg.p_paddr,
255            })?
256            .checked_sub(reloc_bias)
257            .ok_or(Error::RelocBiasOverflow {
258                load_offset,
259                reloc_bias,
260                p_paddr: seg.p_paddr,
261            })?;
262
263        if mem_offset < start_address {
264            return Err(Error::InvalidProgramHeaderMemoryOffset {
265                mem_offset,
266                start_address,
267            });
268        }
269
270        if seg.p_memsz > 0 {
271            buf.import_file_region(
272                importer,
273                ImportFileRegion {
274                    file: kernel_image,
275                    file_offset: seg.p_offset,
276                    file_length: seg.p_filesz,
277                    gpa: mem_offset,
278                    memory_length: seg.p_memsz,
279                    acceptance,
280                    tag,
281                },
282            )
283            .map_err(Error::ImportFileRegion)?;
284        }
285    }
286
287    Ok(LoadInfo {
288        minimum_address_used: lowest_addr - reloc_bias,
289        next_available_address: last_offset - reloc_bias,
290        entrypoint: entry - reloc_bias,
291    })
292}