Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/kernel_loader/src/multiboot.rs
5394 views
1
// Copyright 2024 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
//! Multiboot kernel loader
6
//!
7
//! Only Multiboot (version 0.6.96) is supported, not Multiboot2.
8
9
use std::fs::File;
10
use std::mem::size_of;
11
use std::num::NonZeroU32;
12
13
use base::error;
14
use base::trace;
15
use base::FileReadWriteAtVolatile;
16
use base::VolatileSlice;
17
use resources::AddressRange;
18
use vm_memory::GuestAddress;
19
use vm_memory::GuestMemory;
20
21
use crate::ElfClass;
22
use crate::Error;
23
use crate::LoadedKernel;
24
use crate::Result;
25
26
/// Multiboot header retrieved from a kernel image.
27
#[derive(Clone, Debug)]
28
pub struct MultibootKernel {
29
/// Byte offset of the beginning of the multiboot header in the kernel image.
30
pub offset: u32,
31
32
/// Kernel requires that boot modules are aligned to 4 KB.
33
pub boot_modules_page_aligned: bool,
34
35
/// Kernel requires available memory information (`mem_*` fields).
36
pub need_available_memory: bool,
37
38
/// Kernel load address.
39
///
40
/// If present, this overrides any other executable format headers (e.g. ELF).
41
pub load: Option<MultibootLoad>,
42
43
/// Kernel preferred video mode.
44
///
45
/// If present, the kernel also requires information about the video mode table.
46
pub preferred_video_mode: Option<MultibootVideoMode>,
47
}
48
49
/// Multiboot kernel load parameters.
50
#[derive(Clone, Debug)]
51
pub struct MultibootLoad {
52
/// File byte offset to load the kernel's code and initialized data from.
53
pub file_load_offset: u64,
54
55
/// Number of bytes to read from the file at `file_load_offset`.
56
pub file_load_size: usize,
57
58
/// Physical memory address where the kernel should be loaded.
59
pub load_addr: GuestAddress,
60
61
/// Physical address of the kernel entry point.
62
pub entry_addr: GuestAddress,
63
64
/// BSS physical memory starting address to zero fill, if present in kernel.
65
pub bss_addr: Option<GuestAddress>,
66
67
/// BSS size in bytes (0 if no BSS region is present).
68
pub bss_size: usize,
69
}
70
71
/// Multiboot kernel video mode specification.
72
#[derive(Clone, Debug)]
73
pub struct MultibootVideoMode {
74
/// Preferred video mode type (text or graphics).
75
pub mode_type: MultibootVideoModeType,
76
77
/// Width of the requested mode.
78
///
79
/// For text modes, this is in units of characters. For graphics modes, this is in units of
80
/// pixels.
81
pub width: Option<NonZeroU32>,
82
83
/// Height of the requested mode.
84
///
85
/// For text modes, this is in units of characters. For graphics modes, this is in units of
86
/// pixels.
87
pub height: Option<NonZeroU32>,
88
89
/// Requested bits per pixel (only relevant in graphics modes).
90
pub depth: Option<NonZeroU32>,
91
}
92
93
#[derive(Copy, Clone, Debug)]
94
pub enum MultibootVideoModeType {
95
LinearGraphics,
96
EgaText,
97
Other(u32),
98
}
99
100
/// Scan the provided kernel file to find a Multiboot header, if present.
101
///
102
/// # Returns
103
///
104
/// - `Ok(None)`: kernel file did not contain a Multiboot header.
105
/// - `Ok(Some(...))`: kernel file contained a valid Multiboot header, which is returned.
106
/// - `Err(...)`: kernel file contained a Multiboot header with a valid checksum but other fields in
107
/// the header were invalid.
108
pub fn multiboot_header_from_file(kernel_file: &mut File) -> Result<Option<MultibootKernel>> {
109
const MIN_HEADER_SIZE: usize = 3 * size_of::<u32>();
110
const ALIGNMENT: usize = 4;
111
112
// Read up to 8192 bytes from the beginning of the file.
113
let kernel_file_len = kernel_file.metadata().map_err(|_| Error::ReadHeader)?.len();
114
let kernel_prefix_len = kernel_file_len.min(8192) as usize;
115
116
if kernel_prefix_len < MIN_HEADER_SIZE {
117
return Ok(None);
118
}
119
120
let mut kernel_bytes = vec![0u8; kernel_prefix_len];
121
kernel_file
122
.read_exact_at_volatile(VolatileSlice::new(&mut kernel_bytes), 0)
123
.map_err(|_| Error::ReadHeader)?;
124
125
for offset in (0..kernel_prefix_len).step_by(ALIGNMENT) {
126
let Some(hdr) = kernel_bytes.get(offset..) else {
127
break;
128
};
129
match multiboot_header(hdr, offset as u64, kernel_file_len) {
130
Ok(None) => continue,
131
Ok(Some(multiboot)) => return Ok(Some(multiboot)),
132
Err(e) => return Err(e),
133
}
134
}
135
136
// The file did not contain a valid Multiboot header.
137
Ok(None)
138
}
139
140
/// Attempt to parse a Multiboot header from the prefix of a slice.
141
///
142
/// # Returns
143
///
144
/// - `Ok(None)`: no multiboot header here.
145
/// - `Ok(Some(...))`: valid multiboot header is returned.
146
/// - `Err(...)`: valid multiboot header checksum at this position in the file (meaning this is the
147
/// real header location), but there is an invalid field later in the multiboot header (e.g. an
148
/// impossible combination of load addresses).
149
fn multiboot_header(
150
hdr: &[u8],
151
offset: u64,
152
kernel_file_len: u64,
153
) -> Result<Option<MultibootKernel>> {
154
const MAGIC: u32 = 0x1BADB002;
155
156
let Ok(magic) = get_le32(hdr, 0) else {
157
return Ok(None);
158
};
159
if magic != MAGIC {
160
return Ok(None);
161
}
162
163
// Failing to read these fields means we ran out of data at the end of the slice and did not
164
// actually find a Multiboot header, so return `Ok(None)` to indicate no Multiboot header was
165
// found instead of using `?`, which would return an error.
166
let Ok(flags) = get_le32(hdr, 4) else {
167
return Ok(None);
168
};
169
let Ok(checksum) = get_le32(hdr, 8) else {
170
return Ok(None);
171
};
172
173
if magic.wrapping_add(flags).wrapping_add(checksum) != 0 {
174
// Checksum did not match, so this is not a real Multiboot header. Keep searching.
175
return Ok(None);
176
}
177
178
trace!("found Multiboot header with valid checksum at {offset:#X}");
179
180
const F_BOOT_MODULE_PAGE_ALIGN: u32 = 1 << 0;
181
const F_AVAILABLE_MEMORY: u32 = 1 << 1;
182
const F_VIDEO_MODE: u32 = 1 << 2;
183
const F_ADDRESS: u32 = 1 << 16;
184
185
const KNOWN_FLAGS: u32 =
186
F_BOOT_MODULE_PAGE_ALIGN | F_AVAILABLE_MEMORY | F_VIDEO_MODE | F_ADDRESS;
187
188
let unknown_flags = flags & !KNOWN_FLAGS;
189
if unknown_flags != 0 {
190
error!("unknown flags {unknown_flags:#X}");
191
return Err(Error::InvalidFlags);
192
}
193
194
let boot_modules_page_aligned = flags & F_BOOT_MODULE_PAGE_ALIGN != 0;
195
let need_available_memory = flags & F_AVAILABLE_MEMORY != 0;
196
let need_video_mode_table = flags & F_VIDEO_MODE != 0;
197
let load_address_available = flags & F_ADDRESS != 0;
198
199
let load = if load_address_available {
200
let header_addr = get_le32(hdr, 12)?;
201
let load_addr = get_le32(hdr, 16)?;
202
let load_end_addr = get_le32(hdr, 20)?;
203
let bss_end_addr = get_le32(hdr, 24)?;
204
let entry_addr = get_le32(hdr, 28)?;
205
206
if header_addr < load_addr {
207
error!("header_addr {header_addr:#X} < load_addr {load_addr:#X}");
208
return Err(Error::InvalidKernelOffset);
209
}
210
211
// The beginning of the area to load from the file starts `load_offset` bytes before the
212
// multiboot header.
213
let load_offset = u64::from(header_addr - load_addr);
214
if load_offset > offset {
215
error!("load_offset {load_offset:#X} > offset {offset:#X}");
216
return Err(Error::InvalidKernelOffset);
217
}
218
let file_load_offset = offset - load_offset;
219
220
let file_load_size = if load_end_addr == 0 {
221
// Zero `load_end_addr` means the loadable data extends to the end of the file.
222
(kernel_file_len - file_load_offset)
223
.try_into()
224
.map_err(|_| Error::InvalidKernelOffset)?
225
} else if load_end_addr < load_addr {
226
error!("load_end_addr {load_end_addr:#X} < load_addr {load_addr:#X}");
227
return Err(Error::InvalidKernelOffset);
228
} else {
229
load_end_addr - load_addr
230
};
231
232
let load_end_addr = load_addr
233
.checked_add(file_load_size)
234
.ok_or(Error::InvalidKernelOffset)?;
235
236
// The bss region immediately follows the load-from-file region in memory.
237
let bss_addr = load_addr + file_load_size;
238
239
let bss_size = if bss_end_addr == 0 {
240
// Zero `bss_end_addr` means no bss segment is present.
241
0
242
} else if bss_end_addr < bss_addr {
243
error!("bss_end_addr {bss_end_addr:#X} < bss_addr {bss_addr:#X}");
244
return Err(Error::InvalidKernelOffset);
245
} else {
246
bss_end_addr - bss_addr
247
};
248
249
let bss_addr = if bss_size > 0 {
250
Some(GuestAddress(bss_addr.into()))
251
} else {
252
None
253
};
254
255
if entry_addr < load_addr || entry_addr >= load_end_addr {
256
error!(
257
"entry_addr {entry_addr:#X} not in load range {load_addr:#X}..{load_end_addr:#X}"
258
);
259
return Err(Error::InvalidKernelOffset);
260
}
261
262
Some(MultibootLoad {
263
file_load_offset,
264
file_load_size: file_load_size as usize,
265
load_addr: GuestAddress(load_addr.into()),
266
entry_addr: GuestAddress(entry_addr.into()),
267
bss_addr,
268
bss_size: bss_size as usize,
269
})
270
} else {
271
None
272
};
273
274
let preferred_video_mode = if need_video_mode_table {
275
let mode_type = get_le32(hdr, 32)?;
276
let width = get_le32(hdr, 36)?;
277
let height = get_le32(hdr, 40)?;
278
let depth = get_le32(hdr, 44)?;
279
280
let mode_type = match mode_type {
281
0 => MultibootVideoModeType::LinearGraphics,
282
1 => MultibootVideoModeType::EgaText,
283
_ => MultibootVideoModeType::Other(mode_type),
284
};
285
286
Some(MultibootVideoMode {
287
mode_type,
288
width: NonZeroU32::new(width),
289
height: NonZeroU32::new(height),
290
depth: NonZeroU32::new(depth),
291
})
292
} else {
293
None
294
};
295
296
let multiboot = MultibootKernel {
297
offset: offset as u32,
298
boot_modules_page_aligned,
299
need_available_memory,
300
load,
301
preferred_video_mode,
302
};
303
304
trace!("validated header: {multiboot:?}");
305
306
Ok(Some(multiboot))
307
}
308
309
fn get_le32(bytes: &[u8], offset: usize) -> Result<u32> {
310
let le32_bytes = bytes.get(offset..offset + 4).ok_or(Error::ReadHeader)?;
311
// This can't fail because the slice is always 4 bytes long.
312
let le32_array: [u8; 4] = le32_bytes.try_into().unwrap();
313
Ok(u32::from_le_bytes(le32_array))
314
}
315
316
/// Load a Multiboot kernel image into memory.
317
///
318
/// The `MultibootLoad` information can be retrieved from the optional `load` field of a
319
/// `MultibootKernel` returned by [`multiboot_header_from_file()`].
320
pub fn load_multiboot<F>(
321
guest_mem: &GuestMemory,
322
kernel_image: &mut F,
323
multiboot_load: &MultibootLoad,
324
) -> Result<LoadedKernel>
325
where
326
F: FileReadWriteAtVolatile,
327
{
328
let guest_slice = guest_mem
329
.get_slice_at_addr(multiboot_load.load_addr, multiboot_load.file_load_size)
330
.map_err(|_| Error::ReadKernelImage)?;
331
kernel_image
332
.read_exact_at_volatile(guest_slice, multiboot_load.file_load_offset)
333
.map_err(|_| Error::ReadKernelImage)?;
334
335
if let Some(bss_addr) = multiboot_load.bss_addr {
336
let bss_slice = guest_mem
337
.get_slice_at_addr(bss_addr, multiboot_load.bss_size)
338
.map_err(|_| Error::ReadKernelImage)?;
339
bss_slice.write_bytes(0);
340
}
341
342
let size: u64 = multiboot_load
343
.file_load_size
344
.checked_add(multiboot_load.bss_size)
345
.ok_or(Error::InvalidProgramHeaderSize)?
346
.try_into()
347
.map_err(|_| Error::InvalidProgramHeaderSize)?;
348
349
let address_range = AddressRange::from_start_and_size(multiboot_load.load_addr.offset(), size)
350
.ok_or(Error::InvalidProgramHeaderSize)?;
351
352
Ok(LoadedKernel {
353
address_range,
354
size,
355
entry: multiboot_load.entry_addr,
356
class: ElfClass::ElfClass32,
357
})
358
}
359
360