• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
8 use std::cmp::Ordering;
9 use std::io;
10 use std::mem::offset_of;
11 
12 use base::debug;
13 use base::FileGetLen;
14 use base::FileReadWriteAtVolatile;
15 use base::VolatileSlice;
16 use remain::sorted;
17 use resources::AddressRange;
18 use thiserror::Error;
19 use vm_memory::GuestAddress;
20 use vm_memory::GuestMemory;
21 use vm_memory::GuestMemoryError;
22 use zerocopy::IntoBytes;
23 
24 use crate::bootparam::boot_params;
25 use crate::bootparam::XLF_KERNEL_64;
26 use crate::CpuMode;
27 use crate::KERNEL_32BIT_ENTRY_OFFSET;
28 use crate::KERNEL_64BIT_ENTRY_OFFSET;
29 
30 #[sorted]
31 #[derive(Error, Debug)]
32 pub 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 
57 pub 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.
load_bzimage<F>( guest_mem: &GuestMemory, kernel_start: GuestAddress, kernel_image: &mut F, ) -> Result<(boot_params, AddressRange, GuestAddress, CpuMode)> where F: FileReadWriteAtVolatile + FileGetLen,68 pub fn load_bzimage<F>(
69     guest_mem: &GuestMemory,
70     kernel_start: GuestAddress,
71     kernel_image: &mut F,
72 ) -> Result<(boot_params, AddressRange, GuestAddress, CpuMode)>
73 where
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 }
175