Skip to main content

multiboot2_header/
header.rs

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
14/// Magic value for a [`Header`], as defined by the spec.
15pub const MAGIC: u32 = 0xe85250d6;
16/// Range from the beginning of an image in which bootloaders will search for a
17/// multiboot2 header.
18pub const HEADER_SEARCH_LIMIT: usize = 32768;
19
20/// A parsed complete Multiboot2 header.
21///
22/// It consists of the fixed [`Multiboot2BasicHeader`] prefix followed by all
23/// header tags (see [`HeaderTagType`]). [`Multiboot2BasicHeader`] represents
24/// only that prefix; use this type when working with the complete, dynamically
25/// sized header.
26///
27/// Use this to parse a header from a pointer. To construct a header, use
28/// `Builder` (requires the `builder` feature).
29#[repr(transparent)]
30#[derive(PartialEq, Eq)]
31pub struct Header<'a>(&'a DynSizedStructure<Multiboot2BasicHeader>);
32
33impl<'a> Header<'a> {
34    /// Loads a complete [`Header`] from a pointer.
35    ///
36    /// If the header is invalid, it returns a [`LoadError`].
37    /// This may be because:
38    /// - `ptr` is a null pointer
39    /// - `ptr` is not 8-byte aligned
40    /// - the reported total size is invalid
41    /// - the magic value of the header is not present
42    /// - the checksum field is invalid
43    /// - the tag sequence is incomplete or malformed
44    /// - the mandatory end tag is missing
45    ///
46    /// # Safety
47    ///
48    /// * `ptr` must be valid for reading the complete reported header size.
49    ///   Otherwise, this function might cause invalid machine state or crash
50    ///   your binary.
51    /// * The memory at `ptr` must not be modified after calling `load` or the
52    ///   program may observe unsynchronized mutation.
53    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        // SAFETY: `ptr` was checked for null and the DST constructor
56        // validates size and layout.
57        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    /// Checks whether the header has a valid, complete tag sequence.
74    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            // The spec only requires the end tag to have type 0 and size 8; it
80            // does not constrain the flags field.
81            typ == HeaderTagType::End as u16 && size == size_of::<HeaderTagHeader>()
82        })
83    }
84
85    /// Tries finding a Multiboot2 header in a given slice of binary data.
86    ///
87    /// Performs basic checks, such as length checks and a checksum match.
88    ///
89    /// The Multiboot2 header must be contained completely within the first
90    /// [`HEADER_SEARCH_LIMIT`] bytes of the OS image, and must be
91    /// [64-bit aligned](ALIGNMENT).
92    ///
93    /// On success, it returns the parsed header and an index into the original
94    /// buffer pointing to where the header starts.
95    ///
96    /// # Parameters
97    /// - `buffer`: [64-bit aligned](ALIGNMENT) buffer describing the first
98    ///   [`HEADER_SEARCH_LIMIT`] bytes of a potential Multiboot2 kernel image.
99    pub fn find_header(buffer: &[u8]) -> Result<(Self, usize /* index in buffer */), 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            // Index now points into the original byte buffer.
115            .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        // After that, `u32_iter` continues at the next index after the magic.
123        let (magic_begin_idx, _) = u32_iter
124            // The 64-bit-aligned header starts with magic, so magic must be
125            // aligned too.
126            .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        // After that, `u32_iter` continues at the next index after the size.
134        let (_, header_size) = u32_iter
135            // skip the arch field
136            .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        // Check if the remaining length of the buffer contains the expected
155        // memory.
156        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        // SAFETY: `ptr` points into `buffer`, which has been checked
170        // for alignment and bounds.
171        let header = unsafe { Self::load(ptr)? };
172        Ok((header, magic_begin_idx))
173    }
174
175    /// Returns a [`TagIter`].
176    #[must_use]
177    pub fn iter(&self) -> TagIter<'_> {
178        // SAFETY: `load()` validated the tag chain, and the iterator
179        // only walks that validated payload.
180        unsafe { TagIter::new(self.0.payload()) }
181    }
182
183    /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`].
184    pub const fn verify_checksum(
185        &self,
186    ) -> Result<
187        (),
188        (
189            u32, /* actual checksum */
190            u32, /* expected checksum */
191        ),
192    > {
193        self.0.header().verify_checksum()
194    }
195    /// Wrapper around [`Multiboot2BasicHeader::header_magic`].
196    #[must_use]
197    pub const fn header_magic(&self) -> u32 {
198        self.0.header().header_magic()
199    }
200    /// Wrapper around [`Multiboot2BasicHeader::arch`].
201    #[must_use]
202    pub const fn arch(&self) -> HeaderTagISA {
203        self.0.header().arch()
204    }
205    /// Wrapper around [`Multiboot2BasicHeader::length`].
206    #[must_use]
207    pub const fn length(&self) -> u32 {
208        self.0.header().length()
209    }
210    /// Wrapper around [`Multiboot2BasicHeader::checksum`].
211    #[must_use]
212    pub const fn checksum(&self) -> u32 {
213        self.0.header().checksum()
214    }
215    /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`].
216    #[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    /// Search for the [`InformationRequestHeaderTag`] header tag.
222    #[must_use]
223    pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
224        self.get_tag()
225    }
226
227    /// Search for the [`AddressHeaderTag`] header tag.
228    #[must_use]
229    pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
230        self.get_tag()
231    }
232
233    /// Search for the [`EntryAddressHeaderTag`] header tag.
234    #[must_use]
235    pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
236        self.get_tag()
237    }
238
239    /// Search for the [`EntryEfi32HeaderTag`] header tag.
240    #[must_use]
241    pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
242        self.get_tag()
243    }
244
245    /// Search for the [`EntryEfi64HeaderTag`] header tag.
246    #[must_use]
247    pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
248        self.get_tag()
249    }
250
251    /// Search for the [`ConsoleHeaderTag`] header tag.
252    #[must_use]
253    pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
254        self.get_tag()
255    }
256
257    /// Search for the [`FramebufferHeaderTag`] header tag.
258    #[must_use]
259    pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
260        self.get_tag()
261    }
262
263    /// Search for the [`ModuleAlignHeaderTag`] header tag.
264    #[must_use]
265    pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
266        self.get_tag()
267    }
268
269    /// Search for the [`EfiBootServiceHeaderTag`] header tag.
270    #[must_use]
271    pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
272        self.get_tag()
273    }
274
275    /// Search for the [`RelocatableHeaderTag`] header tag.
276    #[must_use]
277    pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
278        self.get_tag()
279    }
280
281    /// Searches for the specified tag by iterating the structure and returns
282    /// the first occurrence, if present.
283    #[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
318/// Formats the on-wire header tag sequence without dumping tag payloads.
319struct 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/// Errors that occur when a chunk of memory can't be parsed as
330/// [`Header`].
331#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
332pub enum LoadError {
333    /// The provided checksum does not match the expected value.
334    #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
335    ChecksumMismatch(u32 /* is */, u32 /* expected */),
336    /// The header does not contain the correct magic number.
337    #[error("header does not contain expected magic value")]
338    MagicNotFound,
339    /// Missing mandatory end tag.
340    #[error("missing mandatory end tag")]
341    NoEndTag,
342    /// The provided memory can't be parsed as a complete [`Header`].
343    /// See [`MemoryError`].
344    #[error("memory can't be parsed as multiboot2 header")]
345    Memory(#[source] MemoryError),
346}
347
348/// The fixed prefix of a Multiboot2 header.
349///
350/// This contains only fields with a compile-time-known layout. It is followed
351/// by a dynamically sized sequence of header tags, so it is not a complete
352/// Multiboot2 header. Use [`Header`] to parse the complete header.
353#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
354#[repr(C, align(8))]
355pub struct Multiboot2BasicHeader {
356    /// Must be the value of [`MAGIC`].
357    header_magic: u32,
358    arch: HeaderTagISA,
359    length: u32,
360    checksum: u32,
361    // Followed by dynamic amount of dynamically sized header tags.
362    // At minimum, the end tag.
363}
364
365impl Multiboot2BasicHeader {
366    #[cfg(feature = "builder")]
367    /// Constructor for the basic header.
368    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    /// Verifies if a Multiboot2 header is valid.
380    pub const fn verify_checksum(
381        &self,
382    ) -> Result<
383        (),
384        (
385            u32, /* actual checksum */
386            u32, /* expected checksum */
387        ),
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    /// Calculates the checksum as described in the spec.
398    #[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    /// Returns the header magic.
404    #[must_use]
405    pub const fn header_magic(&self) -> u32 {
406        self.header_magic
407    }
408
409    /// Returns the [`HeaderTagISA`].
410    #[must_use]
411    pub const fn arch(&self) -> HeaderTagISA {
412        self.arch
413    }
414
415    /// Returns the length.
416    #[must_use]
417    pub const fn length(&self) -> u32 {
418        self.length
419    }
420
421    /// Returns the checksum.
422    #[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            //.field("tags", &self.iter())
447            .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    /// Writes a minimal valid Multiboot2 header into the buffer, consisting
459    /// only of the basic header and an end tag.
460    fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
461        // Aligned magic
462        buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
463        // Architecture
464        buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
465        // Total size
466        buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
467        // Checksum
468        buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
469        // End tag: ID
470        buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
471        // End tag: Flags
472        buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
473        // End tag: Size
474        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        // Unaligned magic
519        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        // SAFETY: The test buffer is aligned and contains a valid
532        // header layout.
533        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        // The spec does not constrain the end tag's flags field, so an end tag
543        // with the optional bit set must still be accepted.
544        let mut bytes = AlignedBytes::new([0; 24]);
545        write_minimal_valid_header_tag(&mut bytes.0);
546        // End tag: flags = optional (bit 0 set).
547        bytes.0[18..20].copy_from_slice(&1_u16.to_le_bytes());
548
549        // SAFETY: The test buffer is aligned and contains a valid
550        // header layout.
551        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        // SAFETY: The test buffer is aligned and contains a valid
564        // header layout.
565        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        // SAFETY: The test buffer is aligned and contains a valid
582        // header layout.
583        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}