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 flags = u16::from_le_bytes(tag[2..4].try_into().unwrap());
78 let size = u32::from_le_bytes(tag[4..8].try_into().unwrap()) as usize;
79
80 typ == HeaderTagType::End as u16
81 && flags == crate::HeaderTagFlag::Required as u16
82 && size == size_of::<HeaderTagHeader>()
83 })
84 }
85
86 pub fn find_header(buffer: &[u8]) -> Result<(Self, usize ), LoadError> {
101 if buffer.len() < size_of::<Multiboot2BasicHeader>() {
102 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
103 }
104 if buffer.as_ptr().align_offset(ALIGNMENT) != 0 {
105 return Err(LoadError::Memory(MemoryError::WrongAlignment));
106 }
107
108 let search_len = buffer.len().min(HEADER_SEARCH_LIMIT);
109 let buffer = &buffer[0..search_len];
110
111 let mut u32_iter = buffer
112 .chunks(size_of::<u32>())
113 .enumerate()
114 .take_while(|(_, chunk)| chunk.len() == size_of::<u32>())
115 .map(|(idx, chunk)| {
117 (
118 idx * size_of::<u32>(),
119 u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]),
120 )
121 });
122
123 let (magic_begin_idx, _) = u32_iter
125 .find(|(idx, value)| {
128 let is_64bit_aligned = idx % ALIGNMENT == 0;
129 let magic_matches = *value == MAGIC;
130 is_64bit_aligned && magic_matches
131 })
132 .ok_or(LoadError::MagicNotFound)?;
133
134 let (_, header_size) = u32_iter
136 .nth(1)
138 .ok_or(LoadError::Memory(MemoryError::ShorterThanHeader))?;
139
140 let header_size = usize::try_from(header_size)
141 .map_err(|_| LoadError::Memory(MemoryError::ShorterThanHeader))?;
142
143 if header_size < size_of::<Multiboot2BasicHeader>() {
144 return Err(LoadError::Memory(MemoryError::ShorterThanHeader));
145 }
146
147 let min_size = size_of::<Multiboot2BasicHeader>() + size_of::<HeaderTagHeader>();
148 if header_size < min_size {
149 return Err(LoadError::Memory(MemoryError::SizeInsufficient(
150 header_size,
151 min_size,
152 )));
153 }
154
155 let remaining = buffer[magic_begin_idx..].len();
158 if remaining < header_size {
159 return Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
160 header_size,
161 remaining,
162 )));
163 }
164
165 let ptr = buffer
166 .as_ptr()
167 .wrapping_add(magic_begin_idx)
168 .cast::<Multiboot2BasicHeader>();
169
170 let header = unsafe { Self::load(ptr)? };
173 Ok((header, magic_begin_idx))
174 }
175
176 #[must_use]
178 pub fn iter(&self) -> TagIter<'_> {
179 unsafe { TagIter::new(self.0.payload()) }
182 }
183
184 pub const fn verify_checksum(
186 &self,
187 ) -> Result<
188 (),
189 (
190 u32, u32, ),
193 > {
194 self.0.header().verify_checksum()
195 }
196 #[must_use]
198 pub const fn header_magic(&self) -> u32 {
199 self.0.header().header_magic()
200 }
201 #[must_use]
203 pub const fn arch(&self) -> HeaderTagISA {
204 self.0.header().arch()
205 }
206 #[must_use]
208 pub const fn length(&self) -> u32 {
209 self.0.header().length()
210 }
211 #[must_use]
213 pub const fn checksum(&self) -> u32 {
214 self.0.header().checksum()
215 }
216 #[must_use]
218 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
219 Multiboot2BasicHeader::calc_checksum(magic, arch, length)
220 }
221
222 #[must_use]
224 pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
225 self.get_tag()
226 }
227
228 #[must_use]
230 pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
231 self.get_tag()
232 }
233
234 #[must_use]
236 pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
237 self.get_tag()
238 }
239
240 #[must_use]
242 pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
243 self.get_tag()
244 }
245
246 #[must_use]
248 pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
249 self.get_tag()
250 }
251
252 #[must_use]
254 pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
255 self.get_tag()
256 }
257
258 #[must_use]
260 pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
261 self.get_tag()
262 }
263
264 #[must_use]
266 pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
267 self.get_tag()
268 }
269
270 #[must_use]
272 pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
273 self.get_tag()
274 }
275
276 #[must_use]
278 pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
279 self.get_tag()
280 }
281
282 #[must_use]
285 fn get_tag<T: Tag<IDType = HeaderTagType, Header = HeaderTagHeader> + ?Sized + 'a>(
286 &'a self,
287 ) -> Option<&'a T>
288 where
289 T::Metadata: Default,
290 {
291 self.iter()
292 .find(|tag| tag.header().typ() == T::ID)
293 .map(|tag| tag.cast::<T>())
294 }
295}
296
297impl Debug for Header<'_> {
298 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
299 f.debug_struct("Header")
300 .field("magic", &self.header_magic())
301 .field("arch", &self.arch())
302 .field("length", &self.length())
303 .field("checksum", &self.checksum())
304 .field("information_request", &self.information_request_tag())
305 .field("address", &self.address_tag())
306 .field("entry_address", &self.entry_address_tag())
307 .field("entry_address_efi32", &self.entry_address_efi32_tag())
308 .field("entry_address_efi64", &self.entry_address_efi64_tag())
309 .field("console_flags", &self.console_flags_tag())
310 .field("framebuffer", &self.framebuffer_tag())
311 .field("module_align", &self.module_align_tag())
312 .field("efi_boot_services", &self.efi_boot_services_tag())
313 .field("relocatable", &self.relocatable_tag())
314 .field("tag_headers", &DebugTagHeaders(self.iter()))
315 .finish()
316 }
317}
318
319struct DebugTagHeaders<'a>(TagIter<'a>);
321
322impl Debug for DebugTagHeaders<'_> {
323 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
324 f.debug_list()
325 .entries(self.0.clone().map(|tag| tag.header()))
326 .finish()
327 }
328}
329
330#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
333pub enum LoadError {
334 #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
336 ChecksumMismatch(u32 , u32 ),
337 #[error("header does not contain expected magic value")]
339 MagicNotFound,
340 #[error("missing mandatory end tag")]
342 NoEndTag,
343 #[error("memory can't be parsed as multiboot2 header")]
346 Memory(#[source] MemoryError),
347}
348
349#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
355#[repr(C, align(8))]
356pub struct Multiboot2BasicHeader {
357 header_magic: u32,
359 arch: HeaderTagISA,
360 length: u32,
361 checksum: u32,
362 }
365
366impl Multiboot2BasicHeader {
367 #[cfg(feature = "builder")]
368 pub(crate) const fn new(arch: HeaderTagISA, length: u32) -> Self {
370 let magic = MAGIC;
371 let checksum = Self::calc_checksum(magic, arch, length);
372 Self {
373 header_magic: magic,
374 arch,
375 length,
376 checksum,
377 }
378 }
379
380 pub const fn verify_checksum(
382 &self,
383 ) -> Result<
384 (),
385 (
386 u32, u32, ),
389 > {
390 let check = Self::calc_checksum(self.header_magic, self.arch, self.length);
391 if check == self.checksum {
392 Ok(())
393 } else {
394 Err((self.checksum, check))
395 }
396 }
397
398 #[must_use]
400 pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 {
401 (0x100000000 - magic as u64 - arch as u64 - length as u64) as u32
402 }
403
404 #[must_use]
406 pub const fn header_magic(&self) -> u32 {
407 self.header_magic
408 }
409
410 #[must_use]
412 pub const fn arch(&self) -> HeaderTagISA {
413 self.arch
414 }
415
416 #[must_use]
418 pub const fn length(&self) -> u32 {
419 self.length
420 }
421
422 #[must_use]
424 pub const fn checksum(&self) -> u32 {
425 self.checksum
426 }
427}
428
429impl DynSizedHeader for Multiboot2BasicHeader {
430 fn total_size(&self) -> usize {
431 self.length as usize
432 }
433
434 fn set_size(&mut self, total_size: usize) {
435 self.length = total_size as u32;
436 self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32);
437 }
438}
439
440impl Debug for Multiboot2BasicHeader {
441 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
442 f.debug_struct("Multiboot2BasicHeader")
443 .field("header_magic", &{ self.header_magic })
444 .field("arch", &{ self.arch })
445 .field("length", &{ self.length })
446 .field("checksum", &{ self.checksum })
447 .finish()
449 }
450}
451
452#[cfg(test)]
453mod tests {
454 use crate::{Header, HeaderTagISA, HeaderTagType, LoadError, MAGIC, Multiboot2BasicHeader};
455 use core::borrow::Borrow;
456 use multiboot2_common::MemoryError;
457 use multiboot2_common::test_utils::AlignedBytes;
458
459 fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
462 buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
464 buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
466 buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
468 buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
470 buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
472 buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
474 buffer[20..24].copy_from_slice(&8_u32.to_le_bytes());
476 }
477
478 #[test]
479 fn test_assert_size() {
480 assert_eq!(size_of::<Multiboot2BasicHeader>(), 4 + 4 + 4 + 4);
481 }
482
483 #[test]
484 fn find_header_handles_short_buffers() {
485 let bytes = AlignedBytes::new([0; 16]);
486
487 assert_eq!(
488 Header::find_header(bytes.borrow()),
489 Err(LoadError::MagicNotFound)
490 );
491 }
492
493 #[test]
494 fn find_header_rejects_truncated_header() {
495 let mut bytes = AlignedBytes::new([0; 16]);
496 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
497 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
498
499 assert_eq!(
500 Header::find_header(bytes.borrow()),
501 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
502 32, 16
503 )))
504 );
505 }
506
507 #[test]
508 fn find_header_searches_full_multiboot2_range() {
509 let mut bytes = AlignedBytes::new([0; 9000]);
510 write_minimal_valid_header_tag(&mut bytes.0[8192..]);
511
512 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
513 assert_eq!(offset, 8192);
514 }
515
516 #[test]
517 fn find_header_skips_unaligned_magic_candidates() {
518 let mut bytes = AlignedBytes::new([0; 40]);
519 bytes.0[4..8].copy_from_slice(&MAGIC.to_le_bytes());
521 write_minimal_valid_header_tag(&mut bytes.0[8..]);
522
523 let (_header, offset) = Header::find_header(bytes.borrow()).unwrap();
524 assert_eq!(offset, 8);
525 }
526
527 #[test]
528 fn load_accepts_minimal_header_with_end_tag() {
529 let mut bytes = AlignedBytes::new([0; 24]);
530 write_minimal_valid_header_tag(&mut bytes.0);
531
532 let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap();
535
536 let debug = format!("{header:?}");
537 assert!(debug.contains("tag_headers"));
538 assert!(debug.contains("End"));
539 }
540
541 #[test]
542 fn load_rejects_missing_end_tag() {
543 let mut bytes = AlignedBytes::new([0; 16]);
544 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 16);
545 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
546 bytes.0[8..12].copy_from_slice(&16_u32.to_le_bytes());
547 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
548
549 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
552
553 assert!(matches!(header, Err(LoadError::NoEndTag)));
554 }
555
556 #[test]
557 fn load_rejects_invalid_inner_tag_size() {
558 let mut bytes = AlignedBytes::new([0; 32]);
559 let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32);
560 bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes());
561 bytes.0[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
562 bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes());
563 bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes());
564 bytes.0[16..18].copy_from_slice(&(HeaderTagType::InformationRequest as u16).to_le_bytes());
565 bytes.0[20..24].copy_from_slice(&24_u32.to_le_bytes());
566
567 let header = unsafe { Header::load(bytes.as_ptr().cast()) };
570
571 assert_eq!(
572 header,
573 Err(LoadError::Memory(MemoryError::InvalidReportedTotalSize(
574 24, 16
575 )))
576 );
577 }
578}