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, MemoryError, Tag, validate_tag_sequence,
11};
12use thiserror::Error;
13
14/// Magic value for a [`Multiboot2Header`], as defined by the spec.
15pub const MAGIC: u32 = 0xe85250d6;
16/// Range from the beginning of an image in that bootloaders will search for a
17/// multiboot2 header.
18pub const HEADER_SEARCH_LIMIT: usize = 32768;
19
20/// Wrapper type around a pointer to the Multiboot2 header.
21///
22/// The Multiboot2 header is the [`Multiboot2BasicHeader`] followed
23/// by all tags (see [`crate::tags::HeaderTagType`]).
24/// Use this if you get a pointer to the header and just want
25/// to parse it. If you want to construct the type by yourself,
26/// please look at `HeaderBuilder` (requires the `builder` feature).
27#[repr(transparent)]
28#[derive(PartialEq, Eq)]
29pub struct Multiboot2Header<'a>(&'a DynSizedStructure<Multiboot2BasicHeader>);
30
31impl<'a> Multiboot2Header<'a> {
32    /// Loads the [`Multiboot2Header`] from a pointer.
33    ///
34    /// If the header is invalid, it returns a [`LoadError`].
35    /// This may be because:
36    /// - `ptr` is a null pointer
37    /// - `ptr` is not 8-byte aligned
38    /// - the reported total size is invalid
39    /// - the magic value of the header is not present
40    /// - the checksum field is invalid
41    /// - the tag sequence is incomplete or malformed
42    /// - the mandatory end tag is missing
43    ///
44    /// # Safety
45    ///
46    /// * `ptr` must be valid for reading the complete reported header size.
47    ///   Otherwise, this function might cause invalid machine state or crash
48    ///   your binary.
49    /// * The memory at `ptr` must not be modified after calling `load` or the
50    ///   program may observe unsynchronized mutation.
51    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        // SAFETY: `ptr` was checked for null and the DST constructor
54        // validates size and layout.
55        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    /// Checks whether the header has a valid, complete tag sequence.
72    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    /// Tries finding a Multiboot2 header in a given slice of binary data.
85    ///
86    /// Performs basic checks, such as length checks and a checksum match.
87    ///
88    /// The Multiboot2 header must be contained completely within the first
89    /// [`HEADER_SEARCH_LIMIT`] bytes of the OS image, and must be
90    /// [64-bit aligned](ALIGNMENT).
91    ///
92    /// On success, it returns the parsed header and an index into the original
93    /// buffer pointing to where the header starts.
94    ///
95    /// # Parameter
96    /// - `buffer`: [64-bit aligned](ALIGNMENT) buffer describing the first
97    ///   [`HEADER_SEARCH_LIMIT`] bytes of a potential Multiboot2 kernel image.
98    pub fn find_header(buffer: &[u8]) -> Result<(Self, usize /* index in buffer */), 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            // Index now points into the original byte buffer.
114            .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        // After that, `u32_iter` continues at the next index after the magic.
122        let (magic_begin_idx, _) = u32_iter
123            // The 64-bit-aligned header starts with magic, so magic must be
124            // aligned too.
125            .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        // After that, `u32_iter` continues at the next index after the size.
133        let (_, header_size) = u32_iter
134            // skip the arch field
135            .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        // Check if the remaining length of the buffer contains the expected
154        // memory.
155        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        // SAFETY: `ptr` points into `buffer`, which has been checked
169        // for alignment and bounds.
170        let header = unsafe { Self::load(ptr)? };
171        Ok((header, magic_begin_idx))
172    }
173
174    /// Returns a [`TagIter`].
175    #[must_use]
176    pub fn iter(&self) -> TagIter<'_> {
177        // SAFETY: `load()` validated the tag chain, and the iterator
178        // only walks that validated payload.
179        unsafe { TagIter::new(self.0.payload()) }
180    }
181
182    /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`].
183    pub const fn verify_checksum(
184        &self,
185    ) -> Result<
186        (),
187        (
188            u32, /* actual checksum */
189            u32, /* expected checksum */
190        ),
191    > {
192        self.0.header().verify_checksum()
193    }
194    /// Wrapper around [`Multiboot2BasicHeader::header_magic`].
195    #[must_use]
196    pub const fn header_magic(&self) -> u32 {
197        self.0.header().header_magic()
198    }
199    /// Wrapper around [`Multiboot2BasicHeader::arch`].
200    #[must_use]
201    pub const fn arch(&self) -> HeaderTagISA {
202        self.0.header().arch()
203    }
204    /// Wrapper around [`Multiboot2BasicHeader::length`].
205    #[must_use]
206    pub const fn length(&self) -> u32 {
207        self.0.header().length()
208    }
209    /// Wrapper around [`Multiboot2BasicHeader::checksum`].
210    #[must_use]
211    pub const fn checksum(&self) -> u32 {
212        self.0.header().checksum()
213    }
214    /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`].
215    #[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    /// Search for the [`InformationRequestHeaderTag`] header tag.
221    #[must_use]
222    pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
223        self.get_tag()
224    }
225
226    /// Search for the [`AddressHeaderTag`] header tag.
227    #[must_use]
228    pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
229        self.get_tag()
230    }
231
232    /// Search for the [`EntryAddressHeaderTag`] header tag.
233    #[must_use]
234    pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
235        self.get_tag()
236    }
237
238    /// Search for the [`EntryEfi32HeaderTag`] header tag.
239    #[must_use]
240    pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
241        self.get_tag()
242    }
243
244    /// Search for the [`EntryEfi64HeaderTag`] header tag.
245    #[must_use]
246    pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
247        self.get_tag()
248    }
249
250    /// Search for the [`ConsoleHeaderTag`] header tag.
251    #[must_use]
252    pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
253        self.get_tag()
254    }
255
256    /// Search for the [`FramebufferHeaderTag`] header tag.
257    #[must_use]
258    pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
259        self.get_tag()
260    }
261
262    /// Search for the [`ModuleAlignHeaderTag`] header tag.
263    #[must_use]
264    pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
265        self.get_tag()
266    }
267
268    /// Search for the [`EfiBootServiceHeaderTag`] header tag.
269    #[must_use]
270    pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
271        self.get_tag()
272    }
273
274    /// Search for the [`RelocatableHeaderTag`] header tag.
275    #[must_use]
276    pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
277        self.get_tag()
278    }
279
280    /// Searches for the specified tag by iterating the structure and returns
281    /// the first occurrence, if present.
282    #[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            // TODO better debug impl
303            .field("tags", &"<tags iter>")
304            .finish()
305    }
306}
307
308/// Errors that occur when a chunk of memory can't be parsed as
309/// [`Multiboot2Header`].
310#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
311pub enum LoadError {
312    /// The provided checksum does not match the expected value.
313    #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
314    ChecksumMismatch(u32 /* is */, u32 /* expected */),
315    /// The header does not contain the correct magic number.
316    #[error("header does not contain expected magic value")]
317    MagicNotFound,
318    /// Missing mandatory end tag.
319    #[error("missing mandatory end tag")]
320    NoEndTag,
321    /// The provided memory can't be parsed as [`Multiboot2Header`].
322    /// See [`MemoryError`].
323    #[error("memory can't be parsed as multiboot2 header")]
324    Memory(#[source] MemoryError),
325}
326
327/// The "basic" Multiboot2 header. This means only the properties, that are known during
328/// compile time. All other information are derived during runtime from the size property.
329#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
330#[repr(C, align(8))]
331pub struct Multiboot2BasicHeader {
332    /// Must be the value of [`MAGIC`].
333    header_magic: u32,
334    arch: HeaderTagISA,
335    length: u32,
336    checksum: u32,
337    // Followed by dynamic amount of dynamically sized header tags.
338    // At minimum, the end tag.
339}
340
341impl Multiboot2BasicHeader {
342    #[cfg(feature = "builder")]
343    /// Constructor for the basic header.
344    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    /// Verifies if a Multiboot2 header is valid.
356    pub const fn verify_checksum(
357        &self,
358    ) -> Result<
359        (),
360        (
361            u32, /* actual checksum */
362            u32, /* expected checksum */
363        ),
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    /// Calculates the checksum as described in the spec.
374    #[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    /// Returns the header magic.
380    #[must_use]
381    pub const fn header_magic(&self) -> u32 {
382        self.header_magic
383    }
384
385    /// Returns the [`HeaderTagISA`].
386    #[must_use]
387    pub const fn arch(&self) -> HeaderTagISA {
388        self.arch
389    }
390
391    /// Returns the length.
392    #[must_use]
393    pub const fn length(&self) -> u32 {
394        self.length
395    }
396
397    /// Returns the checksum.
398    #[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            //.field("tags", &self.iter())
423            .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    /// Writes a minimal valid Multiboot2 header into the buffer, consisting
437    /// only of the basic header and an end tag.
438    fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
439        // Aligned magic
440        buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
441        // Architecture
442        buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
443        // Total size
444        buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
445        // Checksum
446        buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
447        // End tag: ID
448        buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
449        // End tag: Flags
450        buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
451        // End tag: Size
452        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        // Unaligned magic
497        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        // SAFETY: The test buffer is aligned and contains a valid
510        // header layout.
511        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        // SAFETY: The test buffer is aligned and contains a valid
525        // header layout.
526        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        // SAFETY: The test buffer is aligned and contains a valid
543        // header layout.
544        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}