Skip to main content

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