kernel_loader/
multiboot.rs1use std::fs::File;
10use std::mem::size_of;
11use std::num::NonZeroU32;
12
13use base::error;
14use base::trace;
15use base::FileReadWriteAtVolatile;
16use base::VolatileSlice;
17use resources::AddressRange;
18use vm_memory::GuestAddress;
19use vm_memory::GuestMemory;
20
21use crate::ElfClass;
22use crate::Error;
23use crate::LoadedKernel;
24use crate::Result;
25
26#[derive(Clone, Debug)]
28pub struct MultibootKernel {
29 pub offset: u32,
31
32 pub boot_modules_page_aligned: bool,
34
35 pub need_available_memory: bool,
37
38 pub load: Option<MultibootLoad>,
42
43 pub preferred_video_mode: Option<MultibootVideoMode>,
47}
48
49#[derive(Clone, Debug)]
51pub struct MultibootLoad {
52 pub file_load_offset: u64,
54
55 pub file_load_size: usize,
57
58 pub load_addr: GuestAddress,
60
61 pub entry_addr: GuestAddress,
63
64 pub bss_addr: Option<GuestAddress>,
66
67 pub bss_size: usize,
69}
70
71#[derive(Clone, Debug)]
73pub struct MultibootVideoMode {
74 pub mode_type: MultibootVideoModeType,
76
77 pub width: Option<NonZeroU32>,
82
83 pub height: Option<NonZeroU32>,
88
89 pub depth: Option<NonZeroU32>,
91}
92
93#[derive(Copy, Clone, Debug)]
94pub enum MultibootVideoModeType {
95 LinearGraphics,
96 EgaText,
97 Other(u32),
98}
99
100pub 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 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 Ok(None)
138}
139
140fn 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 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 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 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 (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 let bss_addr = load_addr + file_load_size;
238
239 let bss_size = if bss_end_addr == 0 {
240 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
309fn get_le32(bytes: &[u8], offset: usize) -> Result<u32> {
310 let le32_bytes = bytes.get(offset..offset + 4).ok_or(Error::ReadHeader)?;
311 let le32_array: [u8; 4] = le32_bytes.try_into().unwrap();
313 Ok(u32::from_le_bytes(le32_array))
314}
315
316pub fn load_multiboot<F>(
321 guest_mem: &GuestMemory,
322 kernel_image: &mut F,
323 multiboot_load: &MultibootLoad,
324) -> Result<LoadedKernel>
325where
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}