From 6e24c7f16712e22c39dd2c2faf4e9988ed79ba09 Mon Sep 17 00:00:00 2001 From: Niklas Sombert Date: Thu, 2 Mar 2023 10:23:45 +0100 Subject: [PATCH 1/5] multiboot2-header: Add function to find the header --- multiboot2-header/src/header.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index c84be710..1951f57a 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -3,6 +3,7 @@ use crate::{ EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, RelocatableHeaderTag, }; +use core::convert::TryInto; use core::fmt::{Debug, Formatter}; use core::mem::size_of; @@ -59,6 +60,28 @@ impl<'a> Multiboot2Header<'a> { Self { inner: reference } } + /// Find the header in a given slice. + pub fn find_header(buffer: &[u8]) -> Option<(&[u8], u32)> { + // the magic is 32 bit aligned and inside the first 8192 bytes + assert!(buffer.len() >= 8192); + let mut chunks = buffer[0..8192].chunks_exact(4); + let magic_index = match chunks.position(|vals| { + u32::from_le_bytes(vals.try_into().unwrap()) // yes, there's 4 bytes here + == MULTIBOOT2_HEADER_MAGIC + }) { + Some(idx) => idx * 4, + None => return None, + }; + chunks.next(); // arch + let header_length: usize = u32::from_le_bytes(chunks.next().unwrap().try_into().unwrap()) + .try_into() + .unwrap(); + Some(( + &buffer[magic_index..magic_index + header_length], + magic_index as u32, + )) + } + /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`]. pub const fn verify_checksum(&self) -> bool { self.inner.verify_checksum() From 18e34d9c63c7e4769aa14d2fc6ac355b0a45ed29 Mon Sep 17 00:00:00 2001 From: Niklas Sombert Date: Tue, 21 Mar 2023 23:59:18 +0100 Subject: [PATCH 2/5] multiboot2-header: Add getters for the tags --- multiboot2-header/src/header.rs | 63 ++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 1951f57a..5c3172b3 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -1,7 +1,8 @@ use crate::{ AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EndHeaderTag, EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, - HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, RelocatableHeaderTag, + HeaderTag, HeaderTagISA, HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, + RelocatableHeaderTag, }; use core::convert::TryInto; use core::fmt::{Debug, Formatter}; @@ -110,6 +111,66 @@ impl<'a> Multiboot2Header<'a> { pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 { Multiboot2BasicHeader::calc_checksum(magic, arch, length) } + + /// Search for the address header tag. + pub fn address_tag(&self) -> Option<&AddressHeaderTag> { + self.get_tag(HeaderTagType::Address) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const AddressHeaderTag) }) + } + + /// Search for the entry address header tag. + pub fn entry_address_tag(&self) -> Option<&EntryAddressHeaderTag> { + self.get_tag(HeaderTagType::EntryAddress) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryAddressHeaderTag) }) + } + + /// Search for the EFI32 entry address header tag. + pub fn entry_address_efi32_tag(&self) -> Option<&EntryEfi32HeaderTag> { + self.get_tag(HeaderTagType::EntryAddressEFI32) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi32HeaderTag) }) + } + + /// Search for the EFI64 entry address header tag. + pub fn entry_address_efi64_tag(&self) -> Option<&EntryEfi64HeaderTag> { + self.get_tag(HeaderTagType::EntryAddressEFI64) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EntryEfi64HeaderTag) }) + } + + /// Search for the console flags header tag. + pub fn console_flags_tag(&self) -> Option<&ConsoleHeaderTag> { + self.get_tag(HeaderTagType::ConsoleFlags) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const ConsoleHeaderTag) }) + } + + /// Search for the framebuffer header tag. + pub fn framebuffer_tag(&self) -> Option<&FramebufferHeaderTag> { + self.get_tag(HeaderTagType::Framebuffer) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const FramebufferHeaderTag) }) + } + + /// Search for the module align header tag. + pub fn module_align_tag(&self) -> Option<&ModuleAlignHeaderTag> { + self.get_tag(HeaderTagType::ModuleAlign) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const ModuleAlignHeaderTag) }) + } + + /// Search for the EFI Boot Services header tag. + pub fn efi_boot_services_tag(&self) -> Option<&EfiBootServiceHeaderTag> { + self.get_tag(HeaderTagType::EfiBS) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const EfiBootServiceHeaderTag) }) + } + + /// Search for the EFI32 entry address header tag. + pub fn relocatable_tag(&self) -> Option<&RelocatableHeaderTag> { + self.get_tag(HeaderTagType::Relocatable) + .map(|tag| unsafe { &*(tag as *const HeaderTag as *const RelocatableHeaderTag) }) + } + + fn get_tag(&self, typ: HeaderTagType) -> Option<&HeaderTag> { + self.iter() + .map(|tag| unsafe { tag.as_ref() }.unwrap()) + .find(|tag| tag.typ() == typ) + } } impl<'a> Debug for Multiboot2Header<'a> { From 80c2137477a2517dee5a2ac37b52472eb2e1f192 Mon Sep 17 00:00:00 2001 From: Niklas Sombert Date: Sat, 25 Mar 2023 00:32:44 +0100 Subject: [PATCH 3/5] multiboot2-header: Don't trip up on ModuleAlign --- multiboot2-header/src/header.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 5c3172b3..b0b86b1e 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -397,6 +397,10 @@ impl Debug for Multiboot2HeaderTagIter { let entry = t as *const EntryEfi64HeaderTag; let entry = &*(entry); debug.entry(entry); + } else if typ == HeaderTagType::ModuleAlign { + let entry = t as *const ModuleAlignHeaderTag; + let entry = &*(entry); + debug.entry(entry); } else if typ == HeaderTagType::Relocatable { let entry = t as *const RelocatableHeaderTag; let entry = &*(entry); From a190e48478bf824bdaa152a7b08c4ca0fc9e7826 Mon Sep 17 00:00:00 2001 From: Niklas Sombert Date: Mon, 15 May 2023 10:39:24 +0200 Subject: [PATCH 4/5] multiboot2-header: Make find_header return a Result This should make the function easier to use: If the header is not properly aligned or cut off, the function previously just returned None or might have even panicked. --- multiboot2-header/src/header.rs | 58 ++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index b0b86b1e..97f78343 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -62,25 +62,51 @@ impl<'a> Multiboot2Header<'a> { } /// Find the header in a given slice. - pub fn find_header(buffer: &[u8]) -> Option<(&[u8], u32)> { + /// + /// If it succeeds, it returns a tuple consisting of the subslice containing + /// just the header and the index of the header in the given slice. + /// If it fails (either because the header is not properply 64-bit aligned + /// or because it is truncated), it returns a [`BufError`]. + /// If there is no header, it returns `None`. + pub fn find_header(buffer: &[u8]) -> Result, BufError> { // the magic is 32 bit aligned and inside the first 8192 bytes assert!(buffer.len() >= 8192); - let mut chunks = buffer[0..8192].chunks_exact(4); - let magic_index = match chunks.position(|vals| { + let mut windows = buffer[0..8192].windows(4); + let magic_index = match windows.position(|vals| { u32::from_le_bytes(vals.try_into().unwrap()) // yes, there's 4 bytes here == MULTIBOOT2_HEADER_MAGIC }) { - Some(idx) => idx * 4, - None => return None, + Some(idx) => { + if idx % 8 == 0 { + idx + } else { + return Err(BufError::Unaligned); + } + } + None => return Ok(None), }; - chunks.next(); // arch - let header_length: usize = u32::from_le_bytes(chunks.next().unwrap().try_into().unwrap()) - .try_into() - .unwrap(); - Some(( + // skip over rest of magic + windows.next(); + windows.next(); + windows.next(); + // arch + windows.next(); + windows.next(); + windows.next(); + windows.next(); + let header_length: usize = u32::from_le_bytes( + windows + .next() + .ok_or(BufError::TooSmall)? + .try_into() + .unwrap(), // 4 bytes are a u32 + ) + .try_into() + .unwrap(); + Ok(Some(( &buffer[magic_index..magic_index + header_length], magic_index as u32, - )) + ))) } /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`]. @@ -181,6 +207,16 @@ impl<'a> Debug for Multiboot2Header<'a> { } } +/// Errors that can occur when parsing a header from a slice. +/// See [`Multiboot2Header::find_header`]. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum BufError { + /// The header in the given slice is truncated. + TooSmall, + /// The header in the given slice is not properly 64-bit aligned. + Unaligned, +} + /// **Use this only if you know what you do. You probably want to use /// [`Multiboot2Header`] instead.** /// From e1b7ffb3f233695e6aca2d12079a644560913666 Mon Sep 17 00:00:00 2001 From: Niklas Sombert Date: Mon, 15 May 2023 13:29:15 +0200 Subject: [PATCH 5/5] multiboot2-header: Make from_addr return a Result --- multiboot2-header/src/header.rs | 61 +++++++++++++++------------------ 1 file changed, 28 insertions(+), 33 deletions(-) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 97f78343..04ef0825 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -23,12 +23,10 @@ pub struct Multiboot2Header<'a> { } impl<'a> Multiboot2Header<'a> { - /// Public constructor for this type with various validations. It panics if the address is invalid. - /// It panics rather than returning a result, because if this fails, it is - /// a fatal, unrecoverable error anyways and a bug in your code. + /// Public constructor for this type with various validations. /// - /// # Panics - /// Panics if one of the following conditions is true: + /// If the header is invalid, it returns a [`LoadError`]. + /// This may be because: /// - `addr` is a null-pointer /// - `addr` isn't 8-byte aligned /// - the magic value of the header is not present @@ -37,28 +35,21 @@ impl<'a> Multiboot2Header<'a> { /// # Safety /// This function may produce undefined behaviour, if the provided `addr` is not a valid /// Multiboot2 header pointer. - pub unsafe fn from_addr(addr: usize) -> Self { - assert_ne!(0, addr, "`addr` is null pointer"); - assert_eq!( - addr % 8, - 0, - "`addr` must be 8-byte aligned, see Multiboot2 spec" - ); + // This function can be `const` on newer Rust versions. + #[allow(clippy::missing_const_for_fn)] + pub unsafe fn from_addr(addr: usize) -> Result { + if addr == 0 || addr % 8 != 0 { + return Err(LoadError::InvalidAddress); + } let ptr = addr as *const Multiboot2BasicHeader; let reference = &*ptr; - assert_eq!( - reference.header_magic(), - MULTIBOOT2_HEADER_MAGIC, - "The Multiboot2 header must contain the MULTIBOOT2_HEADER_MAGIC={:x}", - MULTIBOOT2_HEADER_MAGIC - ); - assert!( - reference.verify_checksum(), - "checksum invalid! Is {:x}, expected {:x}", - reference.checksum(), - Self::calc_checksum(reference.header_magic, reference.arch, reference.length) - ); - Self { inner: reference } + if reference.header_magic() != MULTIBOOT2_HEADER_MAGIC { + return Err(LoadError::MagicNotFound); + } + if !reference.verify_checksum() { + return Err(LoadError::ChecksumMismatch); + } + Ok(Self { inner: reference }) } /// Find the header in a given slice. @@ -66,9 +57,9 @@ impl<'a> Multiboot2Header<'a> { /// If it succeeds, it returns a tuple consisting of the subslice containing /// just the header and the index of the header in the given slice. /// If it fails (either because the header is not properply 64-bit aligned - /// or because it is truncated), it returns a [`BufError`]. + /// or because it is truncated), it returns a [`LoadError`]. /// If there is no header, it returns `None`. - pub fn find_header(buffer: &[u8]) -> Result, BufError> { + pub fn find_header(buffer: &[u8]) -> Result, LoadError> { // the magic is 32 bit aligned and inside the first 8192 bytes assert!(buffer.len() >= 8192); let mut windows = buffer[0..8192].windows(4); @@ -80,7 +71,7 @@ impl<'a> Multiboot2Header<'a> { if idx % 8 == 0 { idx } else { - return Err(BufError::Unaligned); + return Err(LoadError::InvalidAddress); } } None => return Ok(None), @@ -97,7 +88,7 @@ impl<'a> Multiboot2Header<'a> { let header_length: usize = u32::from_le_bytes( windows .next() - .ok_or(BufError::TooSmall)? + .ok_or(LoadError::TooSmall)? .try_into() .unwrap(), // 4 bytes are a u32 ) @@ -210,11 +201,15 @@ impl<'a> Debug for Multiboot2Header<'a> { /// Errors that can occur when parsing a header from a slice. /// See [`Multiboot2Header::find_header`]. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum BufError { - /// The header in the given slice is truncated. +pub enum LoadError { + /// The checksum does not match the data. + ChecksumMismatch, + /// The header is not properly 64-bit aligned (or a null pointer). + InvalidAddress, + /// The header does not contain the correct magic number. + MagicNotFound, + /// The header is truncated. TooSmall, - /// The header in the given slice is not properly 64-bit aligned. - Unaligned, } /// **Use this only if you know what you do. You probably want to use