linux_loader/loader/
mod.rs

1// Copyright © 2020, Oracle and/or its affiliates.
2//
3// Copyright (c) 2019 Intel Corporation. All rights reserved.
4// Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5//
6// Copyright 2017 The Chromium OS Authors. All rights reserved.
7// Use of this source code is governed by a BSD-style license that can be
8// found in the LICENSE-BSD-3-Clause file.
9//
10// SPDX-License-Identifier: Apache-2.0 AND BSD-3-Clause
11
12//! Traits and structs for loading kernels into guest memory.
13//! - [KernelLoader](trait.KernelLoader.html): load kernel image into guest memory.
14//! - [KernelLoaderResult](struct.KernelLoaderResult.html): structure passed to the VMM to assist
15//!   zero page construction and boot environment setup.
16//! - [Elf](elf/struct.Elf.html): elf image loader.
17//! - [BzImage](bzimage/struct.BzImage.html): bzImage loader.
18//! - [PE](pe/struct.PE.html): PE image loader.
19
20extern crate vm_memory;
21
22use std::fmt;
23use std::io::{Read, Seek};
24
25#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
26use vm_memory::ByteValued;
27use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestUsize, ReadVolatile};
28
29#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
30pub use crate::loader_gen::bootparam;
31
32pub use crate::cmdline::Cmdline;
33
34#[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))]
35pub mod pe;
36#[cfg(all(any(target_arch = "aarch64", target_arch = "riscv64"), feature = "pe"))]
37pub use pe::*;
38
39#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))]
40pub mod elf;
41#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "elf"))]
42pub use elf::*;
43
44#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))]
45pub mod bzimage;
46#[cfg(all(any(target_arch = "x86", target_arch = "x86_64"), feature = "bzimage"))]
47pub use bzimage::*;
48
49#[derive(Debug, PartialEq, Eq)]
50/// Kernel loader errors.
51pub enum Error {
52    /// Failed to load bzimage.
53    #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
54    Bzimage(bzimage::Error),
55
56    /// Failed to load elf image.
57    #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
58    Elf(elf::Error),
59
60    /// Failed to load PE image.
61    #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
62    Pe(pe::Error),
63
64    /// Invalid command line.
65    InvalidCommandLine,
66    /// Failed writing command line to guest memory.
67    CommandLineCopy,
68    /// Command line overflowed guest memory.
69    CommandLineOverflow,
70    /// Invalid kernel start address.
71    InvalidKernelStartAddress,
72    /// Memory to load kernel image is too small.
73    MemoryOverflow,
74}
75
76/// A specialized [`Result`] type for the kernel loader.
77///
78/// [`Result`]: https://doc.rust-lang.org/std/result/enum.Result.html
79pub type Result<T> = std::result::Result<T, Error>;
80
81impl fmt::Display for Error {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "Kernel Loader: ")?;
84        match self {
85            #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
86            Error::Bzimage(ref e) => write!(f, "failed to load bzImage kernel image: {e}"),
87            #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
88            Error::Elf(ref e) => write!(f, "failed to load ELF kernel image: {e}"),
89            #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
90            Error::Pe(ref e) => write!(f, "failed to load PE kernel image: {e}"),
91
92            Error::InvalidCommandLine => write!(f, "invalid command line provided"),
93            Error::CommandLineCopy => write!(f, "failed writing command line to guest memory"),
94            Error::CommandLineOverflow => write!(f, "command line overflowed guest memory"),
95            Error::InvalidKernelStartAddress => write!(f, "invalid kernel start address"),
96            Error::MemoryOverflow => write!(f, "memory to load kernel image is not enough"),
97        }
98    }
99}
100
101impl std::error::Error for Error {
102    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
103        match self {
104            #[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
105            Error::Bzimage(ref e) => Some(e),
106            #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
107            Error::Elf(ref e) => Some(e),
108            #[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
109            Error::Pe(ref e) => Some(e),
110
111            Error::InvalidCommandLine => None,
112            Error::CommandLineCopy => None,
113            Error::CommandLineOverflow => None,
114            Error::InvalidKernelStartAddress => None,
115            Error::MemoryOverflow => None,
116        }
117    }
118}
119
120#[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
121impl From<elf::Error> for Error {
122    fn from(err: elf::Error) -> Self {
123        Error::Elf(err)
124    }
125}
126
127#[cfg(all(feature = "bzimage", any(target_arch = "x86", target_arch = "x86_64")))]
128impl From<bzimage::Error> for Error {
129    fn from(err: bzimage::Error) -> Self {
130        Error::Bzimage(err)
131    }
132}
133
134#[cfg(all(feature = "pe", any(target_arch = "aarch64", target_arch = "riscv64")))]
135impl From<pe::Error> for Error {
136    fn from(err: pe::Error) -> Self {
137        Error::Pe(err)
138    }
139}
140
141/// Result of [`KernelLoader.load()`](trait.KernelLoader.html#tymethod.load).
142///
143/// This specifies where the kernel is loading and passes additional
144/// information for the rest of the boot process to be completed by
145/// the VMM.
146#[derive(Clone, Copy, Debug, Default, PartialEq)]
147pub struct KernelLoaderResult {
148    /// Address in the guest memory where the kernel image starts to be loaded.
149    pub kernel_load: GuestAddress,
150    /// Offset in guest memory corresponding to the end of kernel image, in case the device tree
151    /// blob and initrd will be loaded adjacent to kernel image.
152    pub kernel_end: GuestUsize,
153    /// Configuration for the VMM to use to fill zero page for bzImage direct boot.
154    /// See <https://www.kernel.org/doc/Documentation/x86/boot.txt>.
155    #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
156    pub setup_header: Option<bootparam::setup_header>,
157    /// Availability of a PVH entry point. Only used for ELF boot, indicates whether the kernel
158    /// supports the PVH boot protocol as described in:
159    /// <https://xenbits.xen.org/docs/unstable/misc/pvh.html>
160    #[cfg(all(feature = "elf", any(target_arch = "x86", target_arch = "x86_64")))]
161    pub pvh_boot_cap: elf::PvhBootCapability,
162}
163
164/// Trait that specifies kernel image loading support.
165pub trait KernelLoader {
166    /// How to load a specific kernel image format into the guest memory.
167    ///
168    /// # Arguments
169    ///
170    /// * `guest_mem`: [`GuestMemory`] to load the kernel in.
171    /// * `kernel_offset`: Usage varies between implementations.
172    /// * `kernel_image`: Kernel image to be loaded.
173    /// * `highmem_start_address`: Address where high memory starts.
174    ///
175    /// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html
176    fn load<F, M: GuestMemory>(
177        guest_mem: &M,
178        kernel_offset: Option<GuestAddress>,
179        kernel_image: &mut F,
180        highmem_start_address: Option<GuestAddress>,
181    ) -> Result<KernelLoaderResult>
182    where
183        F: Read + ReadVolatile + Seek;
184}
185
186#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
187// SAFETY: The layout of the structure is fixed and can be initialized by
188// reading its content from byte array.
189unsafe impl ByteValued for bootparam::setup_header {}
190
191#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
192// SAFETY: The layout of the structure is fixed and can be initialized by
193// reading its content from byte array.
194unsafe impl ByteValued for bootparam::boot_params {}
195
196/// Writes the command line string to the given guest memory slice.
197///
198/// # Arguments
199///
200/// * `guest_mem` - [`GuestMemory`] that will be partially overwritten by the command line.
201/// * `guest_addr` - The address in `guest_mem` at which to load the command line.
202/// * `cmdline` - The kernel command line.
203///
204/// [`GuestMemory`]: https://docs.rs/vm-memory/latest/vm_memory/guest_memory/trait.GuestMemory.html
205///
206/// # Examples
207///
208/// ```rust
209/// # use std::ffi::CStr;
210/// # extern crate vm_memory;
211/// # use linux_loader::loader::*;
212/// # use vm_memory::{Bytes, GuestAddress};
213/// # type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>;
214/// let mem_size: usize = 0x1000000;
215/// let gm = GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), mem_size)]).unwrap();
216/// let mut cl = Cmdline::new(10).unwrap();
217/// cl.insert("foo", "bar");
218/// let mut buf = vec![0u8;8];
219/// let result = load_cmdline(&gm, GuestAddress(0x1000), &cl).unwrap();
220/// gm.read_slice(buf.as_mut_slice(), GuestAddress(0x1000)).unwrap();
221/// assert_eq!(buf.as_slice(), "foo=bar\0".as_bytes());
222pub fn load_cmdline<M: GuestMemory>(
223    guest_mem: &M,
224    guest_addr: GuestAddress,
225    cmdline: &Cmdline,
226) -> Result<()> {
227    // We need a null terminated string because that's what the Linux
228    // kernel expects when parsing the command line:
229    // https://elixir.bootlin.com/linux/v5.10.139/source/kernel/params.c#L179
230    let cmdline_string = cmdline
231        .as_cstring()
232        .map_err(|_| Error::InvalidCommandLine)?;
233
234    let cmdline_bytes = cmdline_string.as_bytes_with_nul();
235
236    let end = guest_addr
237        // Underflow not possible because the cmdline contains at least
238        // a byte (null terminator)
239        .checked_add((cmdline_bytes.len() - 1) as u64)
240        .ok_or(Error::CommandLineOverflow)?;
241    if end > guest_mem.last_addr() {
242        return Err(Error::CommandLineOverflow);
243    }
244
245    guest_mem
246        .write_slice(cmdline_bytes, guest_addr)
247        .map_err(|_| Error::CommandLineCopy)?;
248
249    Ok(())
250}
251
252#[cfg(test)]
253mod tests {
254    use super::*;
255    use vm_memory::{Address, GuestAddress};
256    type GuestMemoryMmap = vm_memory::GuestMemoryMmap<()>;
257
258    const MEM_SIZE: u64 = 0x100_0000;
259
260    fn create_guest_mem() -> GuestMemoryMmap {
261        GuestMemoryMmap::from_ranges(&[(GuestAddress(0x0), (MEM_SIZE as usize))]).unwrap()
262    }
263
264    #[test]
265    fn test_cmdline_overflow() {
266        let gm = create_guest_mem();
267        let mut cl = Cmdline::new(10).unwrap();
268        cl.insert_str("12345").unwrap();
269
270        let cmdline_address = GuestAddress(u64::MAX - 5);
271        assert_eq!(
272            Err(Error::CommandLineOverflow),
273            load_cmdline(&gm, cmdline_address, &cl)
274        );
275
276        let cmdline_address = GuestAddress(MEM_SIZE - 5);
277        assert_eq!(
278            Err(Error::CommandLineOverflow),
279            load_cmdline(&gm, cmdline_address, &cl)
280        );
281        let cmdline_address = GuestAddress(MEM_SIZE - 6);
282        assert!(load_cmdline(&gm, cmdline_address, &cl).is_ok());
283    }
284
285    #[test]
286    fn test_cmdline_write_end_regresion() {
287        let gm = create_guest_mem();
288        let mut cmdline_address = GuestAddress(45);
289        let sample_buf = &[1; 100];
290
291        // Fill in guest memory with non zero bytes
292        gm.write(sample_buf, cmdline_address).unwrap();
293
294        let mut cl = Cmdline::new(10).unwrap();
295
296        // Test loading an empty cmdline
297        load_cmdline(&gm, cmdline_address, &cl).unwrap();
298        let val: u8 = gm.read_obj(cmdline_address).unwrap();
299        assert_eq!(val, b'\0');
300
301        // Test loading an non-empty cmdline
302        cl.insert_str("123").unwrap();
303        load_cmdline(&gm, cmdline_address, &cl).unwrap();
304
305        let val: u8 = gm.read_obj(cmdline_address).unwrap();
306        assert_eq!(val, b'1');
307        cmdline_address = cmdline_address.unchecked_add(1);
308        let val: u8 = gm.read_obj(cmdline_address).unwrap();
309        assert_eq!(val, b'2');
310        cmdline_address = cmdline_address.unchecked_add(1);
311        let val: u8 = gm.read_obj(cmdline_address).unwrap();
312        assert_eq!(val, b'3');
313        cmdline_address = cmdline_address.unchecked_add(1);
314        let val: u8 = gm.read_obj(cmdline_address).unwrap();
315        assert_eq!(val, b'\0');
316    }
317}