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 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 as u16 && 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: HeaderTagISA,
359 length: u32,
360 checksum: u32,
361 }
364
365impl Multiboot2BasicHeader {
366 #[cfg(feature = "builder")]
367 pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self {
369 let magic = MAGIC;
370 let checksum = Self::calc_checksum(magic, arch, length);
371 Self {
372 header_magic: magic,
373 arch,
374 length,
375 checksum,
376 }
377 }
378
379 pub const fn verify_checksum(
381 &self,
382 ) -> Result<
383 (),
384 (
385 u32, u32, ),
388 > {
389 let check = Self::calc_checksum(self.header_magic, self.arch, self.length);
390 if check == self.checksum {
391 Ok(())
392 } else {
393 Err((self.checksum, check))
394 }
395 }
396
397 #[must_use]
399 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
400 (0x100000000 - magic as u64 - arch as u64 - length as u64) as u32
401 }
402
403 #[must_use]
405 pub const fn header_magic(&self) -> u32 {
406 self.header_magic
407 }
408
409 #[must_use]
411 pub const fn arch(&self) -> HeaderTagISA {
412 self.arch
413 }
414
415 #[must_use]
417 pub const fn length(&self) -> u32 {
418 self.length
419 }
420
421 #[must_use]
423 pub const fn checksum(&self) -> u32 {
424 self.checksum
425 }
426}
427
428impl DynSizedHeader for Multiboot2BasicHeader {
429 fn total_size(&self) -> usize {
430 self.length as usize
431 }
432
433 fn set_size(&mut self, total_size: usize) {
434 self.length = total_size as u32;
435 self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32);
436 }
437}
438
439impl Debug for Multiboot2BasicHeader {
440 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
441 f.debug_struct("Multiboot2BasicHeader")
442 .field("header_magic", &{ self.header_magic })
443 .field("arch", &{ self.arch })
444 .field("length", &{ self.length })
445 .field("checksum", &{ self.checksum })
446 .finish()
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use crate::{Header, HeaderTagISA, HeaderTagType, LoadError, MAGIC, Multiboot2BasicHeader};
454 use core::borrow::Borrow;
455 use multiboot2_common::MemoryError;
456 use multiboot2_common::test_utils::AlignedBytes;
457
458 fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
461 buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
463 buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
465 buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
467 buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
469 buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
471 buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
473 buffer[20..24].copy_from_slice(&8_u32.to_le_bytes());
475 }
476
477 #[test]
478 fn test_assert_size() {
479 assert_eq!(size_of::<Multiboot2BasicHeader>(), 4 + 4 + 4 + 4);
480 }
481
482 #[test]
483 fn find_header_handles_short_buffers() {
484 let bytes = AlignedBytes::new([0; 16]);
485
486 assert_eq!(
487 Header::find_header(bytes.borrow()),
488 Err(LoadError::MagicNotFound)
489 );
490 }
491
492 #[test]
493 fn find_header_rejects_truncated_header() {
494 let mut bytes = AlignedBytes::new([0; 16]);
495 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
496 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
497
498 assert_eq!(
499 Header::find_header(bytes.borrow()),
500 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
501 32, 16
502 )))
503 );
504 }
505
506 #[test]
507 fn find_header_searches_full_multiboot2_range() {
508 let mut bytes = AlignedBytes::new([0; 9000]);
509 write_minimal_valid_header_tag(&mut bytes.0[8192..]);
510
511 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
512 assert_eq!(offset, 8192);
513 }
514
515 #[test]
516 fn find_header_skips_unaligned_magic_candidates() {
517 let mut bytes = AlignedBytes::new([0; 40]);
518 bytes.0[4..8].copy_from_slice(&MAGIC.to_le_bytes());
520 write_minimal_valid_header_tag(&mut bytes.0[8..]);
521
522 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
523 assert_eq!(offset, 8);
524 }
525
526 #[test]
527 fn load_accepts_minimal_header_with_end_tag() {
528 let mut bytes = AlignedBytes::new([0; 24]);
529 write_minimal_valid_header_tag(&mut bytes.0);
530
531 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
534
535 let debug = format!("{header:?}");
536 assert!(debug.contains("tag_headers"));
537 assert!(debug.contains("End"));
538 }
539
540 #[test]
541 fn load_accepts_end_tag_with_optional_flag() {
542 let mut bytes = AlignedBytes::new([0; 24]);
545 write_minimal_valid_header_tag(&mut bytes.0);
546 bytes.0[18..20].copy_from_slice(&1_u16.to_le_bytes());
548
549 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
552 assert!(header.is_ok());
553 }
554
555 #[test]
556 fn load_rejects_missing_end_tag() {
557 let mut bytes = AlignedBytes::new([0; 16]);
558 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 16);
559 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
560 bytes.0[8..12].copy_from_slice(&16_u32.to_le_bytes());
561 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
562
563 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
566
567 assert!(matches!(header, Err(LoadError::NoEndTag)));
568 }
569
570 #[test]
571 fn load_rejects_invalid_inner_tag_size() {
572 let mut bytes = AlignedBytes::new([0; 32]);
573 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32);
574 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
575 bytes.0[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
576 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
577 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
578 bytes.0[16..18].copy_from_slice(&(HeaderTagType::InformationRequest as u16).to_le_bytes());
579 bytes.0[20..24].copy_from_slice(&24_u32.to_le_bytes());
580
581 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
584
585 assert_eq!(
586 header,
587 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
588 24, 16
589 )))
590 );
591 }
592}