1use crate::{
2 AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag,
3 EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagHeader, HeaderTagISA,
4 HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, RelocatableHeaderTag,
5 TagIter,
6};
7use core::fmt::{Debug, Formatter};
8use core::ptr::NonNull;
9use multiboot2_common::{
10 ALIGNMENT, DynSizedStructure, Header, MemoryError, Tag, validate_tag_sequence,
11};
12use thiserror::Error;
13
14pub const MAGIC: u32 = 0xe85250d6;
16pub const HEADER_SEARCH_LIMIT: usize = 32768;
19
20#[repr(transparent)]
28#[derive(PartialEq, Eq)]
29pub struct Multiboot2Header<'a>(&'a DynSizedStructure<Multiboot2BasicHeader>);
30
31impl<'a> Multiboot2Header<'a> {
32 pub unsafe fn load(ptr: *const Multiboot2BasicHeader) -> Result<Self, LoadError> {
52 let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?;
53 let inner = unsafe { DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)? };
56 let this = Self(inner);
57
58 let header = this.0.header();
59 if header.header_magic != MAGIC {
60 return Err(LoadError::MagicNotFound);
61 }
62 header
63 .verify_checksum()
64 .map_err(|x| LoadError::ChecksumMismatch(x.0, x.1))?;
65 if !this.has_valid_tag_sequence().map_err(LoadError::Memory)? {
66 return Err(LoadError::NoEndTag);
67 }
68 Ok(this)
69 }
70
71 fn has_valid_tag_sequence(&self) -> Result<bool, MemoryError> {
73 validate_tag_sequence(self.0.payload(), |tag| {
74 let typ = u16::from_le_bytes(tag[0..2].try_into().unwrap());
75 let flags = u16::from_le_bytes(tag[2..4].try_into().unwrap());
76 let size = u32::from_le_bytes(tag[4..8].try_into().unwrap()) as usize;
77
78 typ == HeaderTagType::End as u16
79 && flags == crate::HeaderTagFlag::Required as u16
80 && size == size_of::<HeaderTagHeader>()
81 })
82 }
83
84 pub fn find_header(buffer: &[u8]) -> Result<(Self, usize ), LoadError> {
99 if buffer.len() < size_of::<Multiboot2BasicHeader>() {
100 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
101 }
102 if buffer.as_ptr().align_offset(ALIGNMENT) != 0 {
103 return Err(LoadError::Memory(MemoryError::WrongAlignment));
104 }
105
106 let search_len = buffer.len().min(HEADER_SEARCH_LIMIT);
107 let buffer = &buffer[0..search_len];
108
109 let mut u32_iter = buffer
110 .chunks(size_of::<u32>())
111 .enumerate()
112 .take_while(|(_, chunk)| chunk.len() == size_of::<u32>())
113 .map(|(idx, chunk)| {
115 (
116 idx * size_of::<u32>(),
117 u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]),
118 )
119 });
120
121 let (magic_begin_idx, _) = u32_iter
123 .find(|(idx, value)| {
126 let is_64bit_aligned = idx % ALIGNMENT == 0;
127 let magic_matches = *value == MAGIC;
128 is_64bit_aligned && magic_matches
129 })
130 .ok_or(LoadError::MagicNotFound)?;
131
132 let (_, header_size) = u32_iter
134 .nth(1)
136 .ok_or(LoadError::Memory(MemoryError::ShorterThanHeader))?;
137
138 let header_size = usize::try_from(header_size)
139 .map_err(|_| LoadError::Memory(MemoryError::ShorterThanHeader))?;
140
141 if header_size < size_of::<Multiboot2BasicHeader>() {
142 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
143 }
144
145 let min_size = size_of::<Multiboot2BasicHeader>() + size_of::<HeaderTagHeader>();
146 if header_size < min_size {
147 return Err(LoadError::Memory(MemoryError::SizeInsufficient(
148 header_size,
149 min_size,
150 )));
151 }
152
153 let remaining = buffer[magic_begin_idx..].len();
156 if remaining < header_size {
157 return Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
158 header_size,
159 remaining,
160 )));
161 }
162
163 let ptr = buffer
164 .as_ptr()
165 .wrapping_add(magic_begin_idx)
166 .cast::<Multiboot2BasicHeader>();
167
168 let header = unsafe { Self::load(ptr)? };
171 Ok((header, magic_begin_idx))
172 }
173
174 #[must_use]
176 pub fn iter(&self) -> TagIter<'_> {
177 unsafe { TagIter::new(self.0.payload()) }
180 }
181
182 pub const fn verify_checksum(
184 &self,
185 ) -> Result<
186 (),
187 (
188 u32, u32, ),
191 > {
192 self.0.header().verify_checksum()
193 }
194 #[must_use]
196 pub const fn header_magic(&self) -> u32 {
197 self.0.header().header_magic()
198 }
199 #[must_use]
201 pub const fn arch(&self) -> HeaderTagISA {
202 self.0.header().arch()
203 }
204 #[must_use]
206 pub const fn length(&self) -> u32 {
207 self.0.header().length()
208 }
209 #[must_use]
211 pub const fn checksum(&self) -> u32 {
212 self.0.header().checksum()
213 }
214 #[must_use]
216 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
217 Multiboot2BasicHeader::calc_checksum(magic, arch, length)
218 }
219
220 #[must_use]
222 pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
223 self.get_tag()
224 }
225
226 #[must_use]
228 pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
229 self.get_tag()
230 }
231
232 #[must_use]
234 pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
235 self.get_tag()
236 }
237
238 #[must_use]
240 pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
241 self.get_tag()
242 }
243
244 #[must_use]
246 pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
247 self.get_tag()
248 }
249
250 #[must_use]
252 pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
253 self.get_tag()
254 }
255
256 #[must_use]
258 pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
259 self.get_tag()
260 }
261
262 #[must_use]
264 pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
265 self.get_tag()
266 }
267
268 #[must_use]
270 pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
271 self.get_tag()
272 }
273
274 #[must_use]
276 pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
277 self.get_tag()
278 }
279
280 #[must_use]
283 fn get_tag<T: Tag<IDType = HeaderTagType, Header = HeaderTagHeader> + ?Sized + 'a>(
284 &'a self,
285 ) -> Option<&'a T>
286 where
287 T::Metadata: Default,
288 {
289 self.iter()
290 .find(|tag| tag.header().typ() == T::ID)
291 .map(|tag| tag.cast::<T>())
292 }
293}
294
295impl Debug for Multiboot2Header<'_> {
296 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
297 f.debug_struct("Multiboot2Header")
298 .field("magic", &self.header_magic())
299 .field("arch", &self.arch())
300 .field("length", &self.length())
301 .field("checksum", &self.checksum())
302 .field("tags", &"<tags iter>")
304 .finish()
305 }
306}
307
308#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
311pub enum LoadError {
312 #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
314 ChecksumMismatch(u32 , u32 ),
315 #[error("header does not contain expected magic value")]
317 MagicNotFound,
318 #[error("missing mandatory end tag")]
320 NoEndTag,
321 #[error("memory can't be parsed as multiboot2 header")]
324 Memory(#[source] MemoryError),
325}
326
327#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330#[repr(C, align(8))]
331pub struct Multiboot2BasicHeader {
332 header_magic: u32,
334 arch: HeaderTagISA,
335 length: u32,
336 checksum: u32,
337 }
340
341impl Multiboot2BasicHeader {
342 #[cfg(feature = "builder")]
343 pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self {
345 let magic = MAGIC;
346 let checksum = Self::calc_checksum(magic, arch, length);
347 Self {
348 header_magic: magic,
349 arch,
350 length,
351 checksum,
352 }
353 }
354
355 pub const fn verify_checksum(
357 &self,
358 ) -> Result<
359 (),
360 (
361 u32, u32, ),
364 > {
365 let check = Self::calc_checksum(self.header_magic, self.arch, self.length);
366 if check == self.checksum {
367 Ok(())
368 } else {
369 Err((self.checksum, check))
370 }
371 }
372
373 #[must_use]
375 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
376 (0x100000000 - magic as u64 - arch as u64 - length as u64) as u32
377 }
378
379 #[must_use]
381 pub const fn header_magic(&self) -> u32 {
382 self.header_magic
383 }
384
385 #[must_use]
387 pub const fn arch(&self) -> HeaderTagISA {
388 self.arch
389 }
390
391 #[must_use]
393 pub const fn length(&self) -> u32 {
394 self.length
395 }
396
397 #[must_use]
399 pub const fn checksum(&self) -> u32 {
400 self.checksum
401 }
402}
403
404impl Header for Multiboot2BasicHeader {
405 fn total_size(&self) -> usize {
406 self.length as usize
407 }
408
409 fn set_size(&mut self, total_size: usize) {
410 self.length = total_size as u32;
411 self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32);
412 }
413}
414
415impl Debug for Multiboot2BasicHeader {
416 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
417 f.debug_struct("Multiboot2Header")
418 .field("header_magic", &{ self.header_magic })
419 .field("arch", &{ self.arch })
420 .field("length", &{ self.length })
421 .field("checksum", &{ self.checksum })
422 .finish()
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use crate::{
430 HeaderTagISA, HeaderTagType, LoadError, MAGIC, Multiboot2BasicHeader, Multiboot2Header,
431 };
432 use core::borrow::Borrow;
433 use multiboot2_common::MemoryError;
434 use multiboot2_common::test_utils::AlignedBytes;
435
436 fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
439 buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
441 buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
443 buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
445 buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
447 buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
449 buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
451 buffer[20..24].copy_from_slice(&8_u32.to_le_bytes());
453 }
454
455 #[test]
456 fn test_assert_size() {
457 assert_eq!(size_of::<Multiboot2BasicHeader>(), 4 + 4 + 4 + 4);
458 }
459
460 #[test]
461 fn find_header_handles_short_buffers() {
462 let bytes = AlignedBytes::new([0; 16]);
463
464 assert_eq!(
465 Multiboot2Header::find_header(bytes.borrow()),
466 Err(LoadError::MagicNotFound)
467 );
468 }
469
470 #[test]
471 fn find_header_rejects_truncated_header() {
472 let mut bytes = AlignedBytes::new([0; 16]);
473 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
474 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
475
476 assert_eq!(
477 Multiboot2Header::find_header(bytes.borrow()),
478 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
479 32, 16
480 )))
481 );
482 }
483
484 #[test]
485 fn find_header_searches_full_multiboot2_range() {
486 let mut bytes = AlignedBytes::new([0; 9000]);
487 write_minimal_valid_header_tag(&mut bytes.0[8192..]);
488
489 let (_header, offset) = Multiboot2Header::find_header(bytes.borrow()).unwrap();
490 assert_eq!(offset, 8192);
491 }
492
493 #[test]
494 fn find_header_skips_unaligned_magic_candidates() {
495 let mut bytes = AlignedBytes::new([0; 40]);
496 bytes.0[4..8].copy_from_slice(&MAGIC.to_le_bytes());
498 write_minimal_valid_header_tag(&mut bytes.0[8..]);
499
500 let (_header, offset) = Multiboot2Header::find_header(bytes.borrow()).unwrap();
501 assert_eq!(offset, 8);
502 }
503
504 #[test]
505 fn load_accepts_minimal_header_with_end_tag() {
506 let mut bytes = AlignedBytes::new([0; 24]);
507 write_minimal_valid_header_tag(&mut bytes.0);
508
509 let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) };
512
513 assert!(header.is_ok());
514 }
515
516 #[test]
517 fn load_rejects_missing_end_tag() {
518 let mut bytes = AlignedBytes::new([0; 16]);
519 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 16);
520 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
521 bytes.0[8..12].copy_from_slice(&16_u32.to_le_bytes());
522 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
523
524 let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) };
527
528 assert!(matches!(header, Err(LoadError::NoEndTag)));
529 }
530
531 #[test]
532 fn load_rejects_invalid_inner_tag_size() {
533 let mut bytes = AlignedBytes::new([0; 32]);
534 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32);
535 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
536 bytes.0[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
537 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
538 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
539 bytes.0[16..18].copy_from_slice(&(HeaderTagType::InformationRequest as u16).to_le_bytes());
540 bytes.0[20..24].copy_from_slice(&24_u32.to_le_bytes());
541
542 let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) };
545
546 assert_eq!(
547 header,
548 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
549 24, 16
550 )))
551 );
552 }
553}