x86_64/
bzimage.rs

1// Copyright 2019 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//! Loader for bzImage-format Linux kernels as described in
6//! <https://www.kernel.org/doc/Documentation/x86/boot.txt>
7
8use std::cmp::Ordering;
9use std::io;
10use std::mem::offset_of;
11
12use base::debug;
13use base::FileGetLen;
14use base::FileReadWriteAtVolatile;
15use base::VolatileSlice;
16use remain::sorted;
17use resources::AddressRange;
18use thiserror::Error;
19use vm_memory::GuestAddress;
20use vm_memory::GuestMemory;
21use vm_memory::GuestMemoryError;
22use zerocopy::IntoBytes;
23
24use crate::bootparam::boot_params;
25use crate::bootparam::XLF_KERNEL_64;
26use crate::CpuMode;
27use crate::KERNEL_32BIT_ENTRY_OFFSET;
28use crate::KERNEL_64BIT_ENTRY_OFFSET;
29
30#[sorted]
31#[derive(Error, Debug)]
32pub enum Error {
33    #[error("bad kernel header signature")]
34    BadSignature,
35    #[error("entry point out of range")]
36    EntryPointOutOfRange,
37    #[error("unable to get kernel file size: {0}")]
38    GetFileLen(io::Error),
39    #[error("guest memory error {0}")]
40    GuestMemoryError(GuestMemoryError),
41    #[error("invalid address range")]
42    InvalidAddressRange,
43    #[error("invalid setup_header_end value {0}")]
44    InvalidSetupHeaderEnd(usize),
45    #[error("invalid setup_sects value {0}")]
46    InvalidSetupSects(u8),
47    #[error("invalid syssize value {0}")]
48    InvalidSysSize(u32),
49    #[error("unable to read boot_params: {0}")]
50    ReadBootParams(io::Error),
51    #[error("unable to read header size: {0}")]
52    ReadHeaderSize(io::Error),
53    #[error("unable to read kernel image: {0}")]
54    ReadKernelImage(io::Error),
55}
56
57pub type Result<T> = std::result::Result<T, Error>;
58
59/// Loads a kernel from a bzImage to a slice
60///
61/// # Arguments
62///
63/// * `guest_mem` - The guest memory region the kernel is written to.
64/// * `kernel_start` - The offset into `guest_mem` at which to load the kernel. The header and setup
65///   code will be loaded before this address such that the actual kernel payload will be located at
66///   `kernel_start`.
67/// * `kernel_image` - Input bzImage.
68pub fn load_bzimage<F>(
69    guest_mem: &GuestMemory,
70    kernel_start: GuestAddress,
71    kernel_image: &mut F,
72) -> Result<(boot_params, AddressRange, GuestAddress, CpuMode)>
73where
74    F: FileReadWriteAtVolatile + FileGetLen,
75{
76    let mut params = boot_params::default();
77
78    // The start of setup header is defined by its offset within boot_params (0x01f1).
79    let setup_header_start = offset_of!(boot_params, hdr);
80
81    // Per x86 Linux 64-bit boot protocol:
82    // "The end of setup header can be calculated as follows: 0x0202 + byte value at offset 0x0201"
83    let mut setup_size_byte = 0u8;
84    kernel_image
85        .read_exact_at_volatile(
86            VolatileSlice::new(std::slice::from_mut(&mut setup_size_byte)),
87            0x0201,
88        )
89        .map_err(Error::ReadHeaderSize)?;
90    let setup_header_end = 0x0202 + usize::from(setup_size_byte);
91
92    debug!(
93        "setup_header file offset range: 0x{:04x}..0x{:04x}",
94        setup_header_start, setup_header_end,
95    );
96
97    // Read `setup_header` into `boot_params`. The bzImage may have a different size of
98    // `setup_header`, so read directly into a byte slice of the outer `boot_params` structure
99    // rather than reading into `params.hdr`. The bounds check in `.get_mut()` will ensure we do not
100    // read beyond the end of `boot_params`.
101    let setup_header_slice = params
102        .as_mut_bytes()
103        .get_mut(setup_header_start..setup_header_end)
104        .ok_or(Error::InvalidSetupHeaderEnd(setup_header_end))?;
105
106    kernel_image
107        .read_exact_at_volatile(
108            VolatileSlice::new(setup_header_slice),
109            setup_header_start as u64,
110        )
111        .map_err(Error::ReadBootParams)?;
112
113    // bzImage header signature "HdrS"
114    if params.hdr.header != 0x53726448 {
115        return Err(Error::BadSignature);
116    }
117
118    let setup_sects = if params.hdr.setup_sects == 0 {
119        4u64
120    } else {
121        params.hdr.setup_sects as u64
122    };
123
124    let setup_size = (setup_sects + 1) * 512;
125    let sys_size = u64::from(params.hdr.syssize) * 16;
126    let expected_size = setup_size + sys_size;
127
128    // Adjust the load address so the kernel payload will end up at the original `kernel_start`
129    // location when loading the entire file (including boot sector/setup sectors).
130    let load_addr = kernel_start
131        .checked_sub(setup_size)
132        .ok_or(Error::InvalidSetupSects(params.hdr.setup_sects))?;
133
134    let file_size = kernel_image.get_len().map_err(Error::GetFileLen)?;
135    let file_size_usize =
136        usize::try_from(file_size).map_err(|_| Error::InvalidSetupSects(params.hdr.setup_sects))?;
137
138    match expected_size.cmp(&file_size) {
139        Ordering::Greater => {
140            // `syssize` from header was larger than the actual file.
141            return Err(Error::InvalidSysSize(params.hdr.syssize));
142        }
143        Ordering::Less => {
144            debug!(
145                "loading {} extra bytes appended to bzImage",
146                file_size - expected_size
147            );
148        }
149        Ordering::Equal => {}
150    }
151
152    // Load the whole kernel image to `load_addr`
153    let guest_slice = guest_mem
154        .get_slice_at_addr(load_addr, file_size_usize)
155        .map_err(Error::GuestMemoryError)?;
156    kernel_image
157        .read_exact_at_volatile(guest_slice, 0)
158        .map_err(Error::ReadKernelImage)?;
159
160    let (entry_offset, cpu_mode) = if params.hdr.xloadflags & XLF_KERNEL_64 != 0 {
161        (KERNEL_64BIT_ENTRY_OFFSET, CpuMode::LongMode)
162    } else {
163        (KERNEL_32BIT_ENTRY_OFFSET, CpuMode::FlatProtectedMode)
164    };
165
166    let bzimage_entry = guest_mem
167        .checked_offset(kernel_start, entry_offset)
168        .ok_or(Error::EntryPointOutOfRange)?;
169
170    let kernel_region = AddressRange::from_start_and_size(load_addr.offset(), file_size)
171        .ok_or(Error::InvalidAddressRange)?;
172
173    Ok((params, kernel_region, bzimage_entry, cpu_mode))
174}