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