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 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    /// Tries finding a Multiboot2 header in a given slice of binary data.
87    ///
88    /// Performs basic checks, such as length checks and a checksum match.
89    ///
90    /// The Multiboot2 header must be contained completely within the first
91    /// [`HEADER_SEARCH_LIMIT`] bytes of the OS image, and must be
92    /// [64-bit aligned](ALIGNMENT).
93    ///
94    /// On success, it returns the parsed header and an index into the original
95    /// buffer pointing to where the header starts.
96    ///
97    /// # Parameters
98    /// - `buffer`: [64-bit aligned](ALIGNMENT) buffer describing the first
99    ///   [`HEADER_SEARCH_LIMIT`] bytes of a potential Multiboot2 kernel image.
100    pub fn find_header(buffer: &[u8]) -> Result<(Self, usize /* index in buffer */), 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            // Index now points into the original byte buffer.
116            .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        // After that, `u32_iter` continues at the next index after the magic.
124        let (magic_begin_idx, _) = u32_iter
125            // The 64-bit-aligned header starts with magic, so magic must be
126            // aligned too.
127            .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        // After that, `u32_iter` continues at the next index after the size.
135        let (_, header_size) = u32_iter
136            // skip the arch field
137            .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        // Check if the remaining length of the buffer contains the expected
156        // memory.
157        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        // SAFETY: `ptr` points into `buffer`, which has been checked
171        // for alignment and bounds.
172        let header = unsafe { Self::load(ptr)? };
173        Ok((header, magic_begin_idx))
174    }
175
176    /// Returns a [`TagIter`].
177    #[must_use]
178    pub fn iter(&self) -> TagIter<'_> {
179        // SAFETY: `load()` validated the tag chain, and the iterator
180        // only walks that validated payload.
181        unsafe { TagIter::new(self.0.payload()) }
182    }
183
184    /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`].
185    pub const fn verify_checksum(
186        &self,
187    ) -> Result<
188        (),
189        (
190            u32, /* actual checksum */
191            u32, /* expected checksum */
192        ),
193    > {
194        self.0.header().verify_checksum()
195    }
196    /// Wrapper around [`Multiboot2BasicHeader::header_magic`].
197    #[must_use]
198    pub const fn header_magic(&self) -> u32 {
199        self.0.header().header_magic()
200    }
201    /// Wrapper around [`Multiboot2BasicHeader::arch`].
202    #[must_use]
203    pub const fn arch(&self) -> HeaderTagISA {
204        self.0.header().arch()
205    }
206    /// Wrapper around [`Multiboot2BasicHeader::length`].
207    #[must_use]
208    pub const fn length(&self) -> u32 {
209        self.0.header().length()
210    }
211    /// Wrapper around [`Multiboot2BasicHeader::checksum`].
212    #[must_use]
213    pub const fn checksum(&self) -> u32 {
214        self.0.header().checksum()
215    }
216    /// Wrapper around [`Multiboot2BasicHeader::calc_checksum`].
217    #[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    /// Search for the [`InformationRequestHeaderTag`] header tag.
223    #[must_use]
224    pub fn information_request_tag(&self) -> Option<&InformationRequestHeaderTag> {
225        self.get_tag()
226    }
227
228    /// Search for the [`AddressHeaderTag`] header tag.
229    #[must_use]
230    pub fn address_tag(&self) -> Option<&AddressHeaderTag> {
231        self.get_tag()
232    }
233
234    /// Search for the [`EntryAddressHeaderTag`] header tag.
235    #[must_use]
236    pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> {
237        self.get_tag()
238    }
239
240    /// Search for the [`EntryEfi32HeaderTag`] header tag.
241    #[must_use]
242    pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> {
243        self.get_tag()
244    }
245
246    /// Search for the [`EntryEfi64HeaderTag`] header tag.
247    #[must_use]
248    pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> {
249        self.get_tag()
250    }
251
252    /// Search for the [`ConsoleHeaderTag`] header tag.
253    #[must_use]
254    pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> {
255        self.get_tag()
256    }
257
258    /// Search for the [`FramebufferHeaderTag`] header tag.
259    #[must_use]
260    pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> {
261        self.get_tag()
262    }
263
264    /// Search for the [`ModuleAlignHeaderTag`] header tag.
265    #[must_use]
266    pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> {
267        self.get_tag()
268    }
269
270    /// Search for the [`EfiBootServiceHeaderTag`] header tag.
271    #[must_use]
272    pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> {
273        self.get_tag()
274    }
275
276    /// Search for the [`RelocatableHeaderTag`] header tag.
277    #[must_use]
278    pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> {
279        self.get_tag()
280    }
281
282    /// Searches for the specified tag by iterating the structure and returns
283    /// the first occurrence, if present.
284    #[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
319/// Formats the on-wire header tag sequence without dumping tag payloads.
320struct 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/// Errors that occur when a chunk of memory can't be parsed as
331/// [`Header`].
332#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Error)]
333pub enum LoadError {
334    /// The provided checksum does not match the expected value.
335    #[error("checksum 0x{0:X} does not match expected value 0x{1:x}")]
336    ChecksumMismatch(u32 /* is */, u32 /* expected */),
337    /// The header does not contain the correct magic number.
338    #[error("header does not contain expected magic value")]
339    MagicNotFound,
340    /// Missing mandatory end tag.
341    #[error("missing mandatory end tag")]
342    NoEndTag,
343    /// The provided memory can't be parsed as a complete [`Header`].
344    /// See [`MemoryError`].
345    #[error("memory can't be parsed as multiboot2 header")]
346    Memory(#[source] MemoryError),
347}
348
349/// The fixed prefix of a Multiboot2 header.
350///
351/// This contains only fields with a compile-time-known layout. It is followed
352/// by a dynamically sized sequence of header tags, so it is not a complete
353/// Multiboot2 header. Use [`Header`] to parse the complete header.
354#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
355#[repr(C, align(8))]
356pub struct Multiboot2BasicHeader {
357    /// Must be the value of [`MAGIC`].
358    header_magic: u32,
359    arch: HeaderTagISA,
360    length: u32,
361    checksum: u32,
362    // Followed by dynamic amount of dynamically sized header tags.
363    // At minimum, the end tag.
364}
365
366impl Multiboot2BasicHeader {
367    #[cfg(feature = "builder")]
368    /// Constructor for the basic header.
369    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    /// Verifies if a Multiboot2 header is valid.
381    pub const fn verify_checksum(
382        &self,
383    ) -> Result<
384        (),
385        (
386            u32, /* actual checksum */
387            u32, /* expected checksum */
388        ),
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    /// Calculates the checksum as described in the spec.
399    #[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    /// Returns the header magic.
405    #[must_use]
406    pub const fn header_magic(&self) -> u32 {
407        self.header_magic
408    }
409
410    /// Returns the [`HeaderTagISA`].
411    #[must_use]
412    pub const fn arch(&self) -> HeaderTagISA {
413        self.arch
414    }
415
416    /// Returns the length.
417    #[must_use]
418    pub const fn length(&self) -> u32 {
419        self.length
420    }
421
422    /// Returns the checksum.
423    #[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            //.field("tags", &self.iter())
448            .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    /// Writes a minimal valid Multiboot2 header into the buffer, consisting
460    /// only of the basic header and an end tag.
461    fn write_minimal_valid_header_tag(buffer: &mut [u8]) {
462        // Aligned magic
463        buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes());
464        // Architecture
465        buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes());
466        // Total size
467        buffer[8..12].copy_from_slice(&24_u32.to_le_bytes());
468        // Checksum
469        buffer[12..16].copy_from_slice(&0x17adaf12_u32.to_le_bytes());
470        // End tag: ID
471        buffer[16..18].copy_from_slice(&0_u16.to_le_bytes());
472        // End tag: Flags
473        buffer[18..20].copy_from_slice(&0_u16.to_le_bytes());
474        // End tag: Size
475        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        // Unaligned magic
520        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        // SAFETY: The test buffer is aligned and contains a valid
533        // header layout.
534        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        // SAFETY: The test buffer is aligned and contains a valid
550        // header layout.
551        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        // SAFETY: The test buffer is aligned and contains a valid
568        // header layout.
569        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}