1use crate::{
2 AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag,
3 EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagHeader, HeaderTagISA,
4 HeaderTagISARaw, HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag,
5 RelocatableHeaderTag, TagIter,
6};
7use core::fmt::{Debug, Formatter};
8use core::ptr::NonNull;
9use multiboot2_common::{
10 ALIGNMENT, DynSizedStructure, Header as DynSizedHeader, 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)]
30#[derive(PartialEq, Eq)]
31pub struct Header<'a>(&'a DynSizedStructure<Multiboot2BasicHeader>);
32
33impl<'a> Header<'a> {
34 pub unsafe fn load(ptr: *const Multiboot2BasicHeader) -> Result<Self, LoadError> {
54 let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?;
55 let inner = unsafe { DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)? };
58 let this = Self(inner);
59
60 let header = this.0.header();
61 if header.header_magic != MAGIC {
62 return Err(LoadError::MagicNotFound);
63 }
64 header
65 .verify_checksum()
66 .map_err(|x| LoadError::ChecksumMismatch(x.0, x.1))?;
67 if !this.has_valid_tag_sequence().map_err(LoadError::Memory)? {
68 return Err(LoadError::NoEndTag);
69 }
70 Ok(this)
71 }
72
73 fn has_valid_tag_sequence(&self) -> Result<bool, MemoryError> {
75 validate_tag_sequence(self.0.payload(), |tag| {
76 let typ = u16::from_le_bytes(tag[0..2].try_into().unwrap());
77 let size = u32::from_le_bytes(tag[4..8].try_into().unwrap()) as usize;
78
79 typ == HeaderTagType::End.val() && size == size_of::<HeaderTagHeader>()
82 })
83 }
84
85 pub fn find_header(buffer: &[u8]) -> Result<(Self, usize ), LoadError> {
100 if buffer.len() < size_of::<Multiboot2BasicHeader>() {
101 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
102 }
103 if buffer.as_ptr().align_offset(ALIGNMENT) != 0 {
104 return Err(LoadError::Memory(MemoryError::WrongAlignment));
105 }
106
107 let search_len = buffer.len().min(HEADER_SEARCH_LIMIT);
108 let buffer = &buffer[0..search_len];
109
110 let mut u32_iter = buffer
111 .chunks(size_of::<u32>())
112 .enumerate()
113 .take_while(|(_, chunk)| chunk.len() == size_of::<u32>())
114 .map(|(idx, chunk)| {
116 (
117 idx * size_of::<u32>(),
118 u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]),
119 )
120 });
121
122 let (magic_begin_idx, _) = u32_iter
124 .find(|(idx, value)| {
127 let is_64bit_aligned = idx % ALIGNMENT == 0;
128 let magic_matches = *value == MAGIC;
129 is_64bit_aligned && magic_matches
130 })
131 .ok_or(LoadError::MagicNotFound)?;
132
133 let (_, header_size) = u32_iter
135 .nth(1)
137 .ok_or(LoadError::Memory(MemoryError::ShorterThanHeader))?;
138
139 let header_size = usize::try_from(header_size)
140 .map_err(|_| LoadError::Memory(MemoryError::ShorterThanHeader))?;
141
142 if header_size < size_of::<Multiboot2BasicHeader>() {
143 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
144 }
145
146 let min_size = size_of::<Multiboot2BasicHeader>() + size_of::<HeaderTagHeader>();
147 if header_size < min_size {
148 return Err(LoadError::Memory(MemoryError::SizeInsufficient(
149 header_size,
150 min_size,
151 )));
152 }
153
154 let remaining = buffer[magic_begin_idx..].len();
157 if remaining < header_size {
158 return Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
159 header_size,
160 remaining,
161 )));
162 }
163
164 let ptr = buffer
165 .as_ptr()
166 .wrapping_add(magic_begin_idx)
167 .cast::<Multiboot2BasicHeader>();
168
169 let header = unsafe { Self::load(ptr)? };
172 Ok((header, magic_begin_idx))
173 }
174
175 #[must_use]
177 pub fn iter(&self) -> TagIter<'_> {
178 unsafe { TagIter::new(self.0.payload()) }
181 }
182
183 pub const fn verify_checksum(
185 &self,
186 ) -> Result<
187 (),
188 (
189 u32, u32, ),
192 > {
193 self.0.header().verify_checksum()
194 }
195 #[must_use]
197 pub const fn header_magic(&self) -> u32 {
198 self.0.header().header_magic()
199 }
200 #[must_use]
202 pub const fn arch(&self) -> HeaderTagISA {
203 self.0.header().arch()
204 }
205 #[must_use]
207 pub const fn length(&self) -> u32 {
208 self.0.header().length()
209 }
210 #[must_use]
212 pub const fn checksum(&self) -> u32 {
213 self.0.header().checksum()
214 }
215 #[must_use]
217 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
218 Multiboot2BasicHeader::calc_checksum(magic, arch, length)
219 }
220
221 #[must_use]
223 pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
224 self.get_tag()
225 }
226
227 #[must_use]
229 pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
230 self.get_tag()
231 }
232
233 #[must_use]
235 pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
236 self.get_tag()
237 }
238
239 #[must_use]
241 pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
242 self.get_tag()
243 }
244
245 #[must_use]
247 pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
248 self.get_tag()
249 }
250
251 #[must_use]
253 pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
254 self.get_tag()
255 }
256
257 #[must_use]
259 pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
260 self.get_tag()
261 }
262
263 #[must_use]
265 pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
266 self.get_tag()
267 }
268
269 #[must_use]
271 pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
272 self.get_tag()
273 }
274
275 #[must_use]
277 pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
278 self.get_tag()
279 }
280
281 #[must_use]
284 fn get_tag<T: Tag<IDType = HeaderTagType, Header = HeaderTagHeader> + ?Sized + 'a>(
285 &'a self,
286 ) -> Option<&'a T>
287 where
288 T::Metadata: Default,
289 {
290 self.iter()
291 .find(|tag| tag.header().typ() == T::ID)
292 .map(|tag| tag.cast::<T>())
293 }
294}
295
296impl Debug for Header<'_> {
297 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
298 f.debug_struct("Header")
299 .field("magic", &self.header_magic())
300 .field("arch", &self.arch())
301 .field("length", &self.length())
302 .field("checksum", &self.checksum())
303 .field("information_request", &self.information_request_tag())
304 .field("address", &self.address_tag())
305 .field("entry_address", &self.entry_address_tag())
306 .field("entry_address_efi32", &self.entry_address_efi32_tag())
307 .field("entry_address_efi64", &self.entry_address_efi64_tag())
308 .field("console_flags", &self.console_flags_tag())
309 .field("framebuffer", &self.framebuffer_tag())
310 .field("module_align", &self.module_align_tag())
311 .field("efi_boot_services", &self.efi_boot_services_tag())
312 .field("relocatable", &self.relocatable_tag())
313 .field("tag_headers", &DebugTagHeaders(self.iter()))
314 .finish()
315 }
316}
317
318struct DebugTagHeaders<'a>(TagIter<'a>);
320
321impl Debug for DebugTagHeaders<'_> {
322 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
323 f.debug_list()
324 .entries(self.0.clone().map(|tag| tag.header()))
325 .finish()
326 }
327}
328
329#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
332pub enum LoadError {
333 #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
335 ChecksumMismatch(u32 , u32 ),
336 #[error("header does not contain expected magic value")]
338 MagicNotFound,
339 #[error("missing mandatory end tag")]
341 NoEndTag,
342 #[error("memory can't be parsed as multiboot2 header")]
345 Memory(#[source] MemoryError),
346}
347
348#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
354#[repr(C, align(8))]
355pub struct Multiboot2BasicHeader {
356 header_magic: u32,
358 arch: HeaderTagISARaw,
359 length: u32,
360 checksum: u32,
361 }
364
365const _: () = assert!(size_of::<Multiboot2BasicHeader>() == 4 + 4 + 4 + 4);
366
367impl Multiboot2BasicHeader {
368 #[cfg(feature = "builder")]
369 pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self {
371 let magic = MAGIC;
372 let checksum = Self::calc_checksum(magic, arch, length);
373 Self {
374 header_magic: magic,
375 arch: HeaderTagISARaw::new(arch.val()),
376 length,
377 checksum,
378 }
379 }
380
381 pub const fn verify_checksum(
383 &self,
384 ) -> Result<
385 (),
386 (
387 u32, u32, ),
390 > {
391 let check = Self::calc_checksum(self.header_magic, self.arch(), self.length);
392 if check == self.checksum {
393 Ok(())
394 } else {
395 Err((self.checksum, check))
396 }
397 }
398
399 #[must_use]
401 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
402 (0x100000000 - magic as u64 - arch.val() as u64 - length as u64) as u32
403 }
404
405 #[must_use]
407 pub const fn header_magic(&self) -> u32 {
408 self.header_magic
409 }
410
411 #[must_use]
413 pub const fn arch(&self) -> HeaderTagISA {
414 HeaderTagISA::from_val(self.arch.get())
415 }
416
417 #[must_use]
419 pub const fn length(&self) -> u32 {
420 self.length
421 }
422
423 #[must_use]
425 pub const fn checksum(&self) -> u32 {
426 self.checksum
427 }
428}
429
430unsafe impl DynSizedHeader for Multiboot2BasicHeader {
433 fn total_size(&self) -> usize {
434 self.length as usize
435 }
436
437 fn set_size(&mut self, total_size: usize) {
438 self.length = total_size as u32;
439 self.checksum = Self::calc_checksum(self.header_magic, self.arch(), total_size as u32);
440 }
441}
442
443impl Debug for Multiboot2BasicHeader {
444 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
445 f.debug_struct("Multiboot2BasicHeader")
446 .field("header_magic", &{ self.header_magic })
447 .field("arch", &{ self.arch })
448 .field("length", &{ self.length })
449 .field("checksum", &{ self.checksum })
450 .finish()
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use crate::{Header, HeaderTagISA, HeaderTagType, LoadError, MAGIC, Multiboot2BasicHeader};
458 use core::borrow::Borrow;
459 use multiboot2_common::MemoryError;
460 use multiboot2_common::test_utils::AlignedBytes;
461
462 fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
465 buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
467 buffer[4..8].copy_from_slice(&HeaderTagISA::I386.val().to_le_bytes());
469 buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
471 buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
473 buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
475 buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
477 buffer[20..24].copy_from_slice(&8_u32.to_le_bytes());
479 }
480
481 #[test]
482 fn find_header_handles_short_buffers() {
483 let bytes = AlignedBytes::new([0; 16]);
484
485 assert_eq!(
486 Header::find_header(bytes.borrow()),
487 Err(LoadError::MagicNotFound)
488 );
489 }
490
491 #[test]
492 fn find_header_rejects_truncated_header() {
493 let mut bytes = AlignedBytes::new([0; 16]);
494 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
495 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
496
497 assert_eq!(
498 Header::find_header(bytes.borrow()),
499 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
500 32, 16
501 )))
502 );
503 }
504
505 #[test]
506 fn find_header_searches_full_multiboot2_range() {
507 let mut bytes = AlignedBytes::new([0; 9000]);
508 write_minimal_valid_header_tag(&mut bytes.0[8192..]);
509
510 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
511 assert_eq!(offset, 8192);
512 }
513
514 #[test]
515 fn find_header_skips_unaligned_magic_candidates() {
516 let mut bytes = AlignedBytes::new([0; 40]);
517 bytes.0[4..8].copy_from_slice(&MAGIC.to_le_bytes());
519 write_minimal_valid_header_tag(&mut bytes.0[8..]);
520
521 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
522 assert_eq!(offset, 8);
523 }
524
525 #[test]
526 fn load_accepts_minimal_header_with_end_tag() {
527 let mut bytes = AlignedBytes::new([0; 24]);
528 write_minimal_valid_header_tag(&mut bytes.0);
529
530 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
533
534 let debug = format!("{header:?}");
535 assert!(debug.contains("tag_headers"));
536 assert!(debug.contains("End"));
537 }
538
539 #[test]
540 fn load_accepts_end_tag_with_optional_flag() {
541 let mut bytes = AlignedBytes::new([0; 24]);
544 write_minimal_valid_header_tag(&mut bytes.0);
545 bytes.0[18..20].copy_from_slice(&1_u16.to_le_bytes());
547
548 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
551 assert!(header.is_ok());
552 }
553
554 #[test]
557 fn load_accepts_unknown_flags_value() {
558 use crate::HeaderTagFlag;
559
560 let mut bytes = AlignedBytes::new([0; 24]);
561 write_minimal_valid_header_tag(&mut bytes.0);
562 bytes.0[18..20].copy_from_slice(&0xFFFF_u16.to_le_bytes());
564
565 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
568
569 let flags = header.iter().next().unwrap().header().flags();
570 assert_eq!(flags, HeaderTagFlag::Custom(0xFFFF));
571 }
572
573 #[test]
576 fn load_accepts_unknown_architecture() {
577 let mut bytes = AlignedBytes::new([0; 24]);
578 write_minimal_valid_header_tag(&mut bytes.0);
579 bytes.0[4..8].copy_from_slice(&3_u32.to_le_bytes());
581 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::Custom(3), 24);
582 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
583
584 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
587
588 assert_eq!(header.arch(), HeaderTagISA::Custom(3));
589 let debug = format!("{header:?}");
591 assert!(debug.contains("Custom(3)"));
592 }
593
594 #[test]
597 fn load_accepts_unknown_tag_type() {
598 let mut bytes = AlignedBytes::new([0; 32]);
599 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32);
600 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
601 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
602 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
603 bytes.0[16..18].copy_from_slice(&0x1337_u16.to_le_bytes());
605 bytes.0[20..24].copy_from_slice(&8_u32.to_le_bytes());
606 bytes.0[28..32].copy_from_slice(&8_u32.to_le_bytes());
608
609 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
612
613 let tag = header.iter().next().unwrap();
614 assert_eq!(tag.header().typ(), HeaderTagType::Custom(0x1337));
615 let debug = format!("{header:?}");
617 assert!(debug.contains("Custom(4919)"));
618 }
619
620 #[test]
621 fn load_rejects_missing_end_tag() {
622 let mut bytes = AlignedBytes::new([0; 16]);
623 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 16);
624 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
625 bytes.0[8..12].copy_from_slice(&16_u32.to_le_bytes());
626 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
627
628 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
631
632 assert!(matches!(header, Err(LoadError::NoEndTag)));
633 }
634
635 #[test]
636 fn load_rejects_invalid_inner_tag_size() {
637 let mut bytes = AlignedBytes::new([0; 32]);
638 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32);
639 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
640 bytes.0[4..8].copy_from_slice(&HeaderTagISA::I386.val().to_le_bytes());
641 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
642 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
643 bytes.0[16..18].copy_from_slice(&HeaderTagType::InformationRequest.val().to_le_bytes());
644 bytes.0[20..24].copy_from_slice(&24_u32.to_le_bytes());
645
646 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
649
650 assert_eq!(
651 header,
652 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
653 24, 16
654 )))
655 );
656 }
657}