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