diff --git a/integration-test/bins/multiboot2_chainloader/src/main.rs b/integration-test/bins/multiboot2_chainloader/src/main.rs index 81a0cca0..8f4fe640 100644 --- a/integration-test/bins/multiboot2_chainloader/src/main.rs +++ b/integration-test/bins/multiboot2_chainloader/src/main.rs @@ -6,9 +6,6 @@ mod multiboot; extern crate alloc; -#[macro_use] -extern crate integration_test_util; - use integration_test_util::init_environment; core::arch::global_asm!(include_str!("start.S"), options(att_syntax)); diff --git a/integration-test/bins/multiboot2_chainloader/src/multiboot.rs b/integration-test/bins/multiboot2_chainloader/src/multiboot.rs index 38b2e934..1ee0b41b 100644 --- a/integration-test/bins/multiboot2_chainloader/src/multiboot.rs +++ b/integration-test/bins/multiboot2_chainloader/src/multiboot.rs @@ -1,7 +1,6 @@ //! Parsing the Multiboot information. Glue code for the [`multiboot`] code. use anyhow::anyhow; -use core::ptr::addr_of_mut; use core::slice; use multiboot::information::{MemoryManagement, Multiboot, PAddr, SIGNATURE_EAX}; @@ -13,7 +12,7 @@ pub fn get_mbi<'a>(magic: u32, ptr: u32) -> anyhow::Result + ?Sized>( // See let alloc_size = increase_to_alignment(tag_size); let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap(); + // SAFETY: `layout` matches the requested allocation size and alignment. let heap_ptr = unsafe { alloc::alloc::alloc(layout) }; assert!(!heap_ptr.is_null()); // write header { let len = size_of::(); - let ptr = core::ptr::addr_of!(header); + let ptr = &raw const header; + // SAFETY: `header` is a fully initialized stack value and `heap_ptr` + // points into the freshly allocated destination buffer. unsafe { ptr::copy_nonoverlapping(ptr.cast::(), heap_ptr, len); } @@ -55,16 +58,21 @@ pub fn new_boxed + ?Sized>( for &bytes in additional_bytes_slices { let len = bytes.len(); let src = bytes.as_ptr(); + let dst = heap_ptr.wrapping_add(write_offset); + // SAFETY: `src` is a valid slice and `dst` stays inside the + // allocated object without overlapping `src`. unsafe { - let dst = heap_ptr.add(write_offset); ptr::copy_nonoverlapping(src, dst, len); - write_offset += len; } + write_offset += len; } } // This is a fat pointer for DSTs and a thin pointer for sized `T`s. + // SAFETY: The allocation was sized for `T` and all bytes up to the + // reported dynamic length were initialized above. let ptr: *mut T = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(&header)); + // SAFETY: `ptr` points to the initialized allocation described above. let reference = unsafe { Box::from_raw(ptr) }; // If this panic triggers, there is a fundamental flaw in my logic. This is diff --git a/multiboot2-common/src/iter.rs b/multiboot2-common/src/iter.rs index 7b029c59..4eb3a863 100644 --- a/multiboot2-common/src/iter.rs +++ b/multiboot2-common/src/iter.rs @@ -31,10 +31,15 @@ pub struct TagIter<'a, H: Header> { impl<'a, H: Header> TagIter<'a, H> { /// Creates a new iterator. + /// + /// # Safety + /// + /// Callers must ensure that the whole chain of tags (with their reported + /// sizes) is valid and fits within the memory slice. #[must_use] // TODO we could take a BytesRef here, but the surrounding code should be // bullet-proof enough. - pub fn new(mem: &'a [u8]) -> Self { + pub unsafe fn new(mem: &'a [u8]) -> Self { // Assert alignment. assert_eq!(mem.as_ptr().align_offset(ALIGNMENT), 0); @@ -55,7 +60,14 @@ impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> { } assert!(self.next_tag_offset < self.buffer.len()); - let ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) }.cast::(); + let ptr = self + .buffer + .as_ptr() + .wrapping_add(self.next_tag_offset) + .cast::(); + // SAFETY: `new()` requires a validated, aligned tag chain and the + // current offset is checked to stay within the buffer before this + // dereference. let tag_hdr = unsafe { &*ptr }; // Get relevant byte portion for the next tag. This includes padding @@ -63,7 +75,7 @@ impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> { // See . let slice = { let from = self.next_tag_offset; - let len = size_of::() + tag_hdr.payload_len(); + let len = tag_hdr.total_size(); let to = from + len; // The size of (the allocation for) a value is always a multiple of @@ -78,6 +90,7 @@ impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> { }; // unwrap: We should not fail at this point. + // In any ::load() before, we already validated the whole chain of tags. let tag = DynSizedStructure::ref_from_slice(slice).unwrap(); Some(tag) } @@ -108,7 +121,8 @@ mod tests { 8, 0, 0, 0, ], ); - let mut iter = TagIter::::new(bytes.borrow()); + // SAFETY: Chain of tags is valid. + let mut iter = unsafe { TagIter::::new(bytes.borrow()) }; let first = iter.next().unwrap(); assert_eq!(first.header().typ(), 0xff); assert_eq!(first.header().size(), 8); diff --git a/multiboot2-common/src/lib.rs b/multiboot2-common/src/lib.rs index af0fe646..04492b40 100644 --- a/multiboot2-common/src/lib.rs +++ b/multiboot2-common/src/lib.rs @@ -63,7 +63,7 @@ //! indicates the total size of the structure. This is roughly translated to the //! following rusty base type: //! -//! ```ignore +//! ```rust,ignore //! #[repr(C, align(8))] //! struct DynStructure { //! header: MyHeader, @@ -91,7 +91,7 @@ //! //! Note that we also have structures (tags) in Multiboot2 that looks like this: //! -//! ```ignore +//! ```rust,ignore //! #[repr(C, align(8))] //! struct DynStructure { //! header: MyHeader, @@ -102,7 +102,7 @@ //! //! or //! -//! ```ignore +//! ```rust,ignore //! #[repr(C, align(8))] //! struct CommandLineTag { //! header: TagHeader, @@ -224,8 +224,9 @@ #![deny( clippy::all, clippy::cargo, - clippy::must_use_candidate, clippy::nursery, + clippy::must_use_candidate, + clippy::undocumented_unsafe_blocks, missing_debug_implementations, missing_docs, rustdoc::all @@ -256,7 +257,6 @@ pub use iter::TagIter; pub use tag::{MaybeDynSized, Tag}; use core::fmt::Debug; -use core::ptr; use core::ptr::NonNull; use core::slice; use thiserror::Error; @@ -274,16 +274,18 @@ pub const ALIGNMENT: usize = 8; /// The alignment of implementors **must** be the compatible with the demands /// for the corresponding structure, which typically is [`ALIGNMENT`]. pub trait Header: Clone + Sized + PartialEq + Eq + Debug { - /// Returns the length of the payload, i.e., the bytes that are additional - /// to the header. The value is measured in bytes. + /// Returns the total size of the structure in bytes, including the fixed + /// header and any dynamic payload. #[must_use] - fn payload_len(&self) -> usize; + fn total_size(&self) -> usize; - /// Returns the total size of the struct, thus the size of the header itself - /// plus [`Header::payload_len`]. + /// Returns the length of the payload, i.e., the bytes that are additional + /// to the header. The value is measured in bytes. #[must_use] - fn total_size(&self) -> usize { - size_of::() + self.payload_len() + fn payload_len(&self) -> usize { + let total_size = self.total_size(); + assert!(total_size >= size_of::()); + total_size - size_of::() } /// Updates the header with the given `total_size`. @@ -294,7 +296,7 @@ pub trait Header: Clone + Sized + PartialEq + Eq + Debug { /// and a dynamic amount of bytes without hidden implicit padding. /// /// This structures combines a [`Header`] with the logically owned data by -/// that header according to the reported [`Header::payload_len`]. Instances +/// that header according to the reported [`Header::total_size`]. Instances /// guarantees that the memory requirements promised in the crates description /// are respected. /// @@ -331,16 +333,22 @@ impl DynSizedStructure { /// from the given [`BytesRef`]. pub fn ref_from_bytes(bytes: BytesRef<'_, H>) -> Result<&Self, MemoryError> { let ptr = bytes.as_ptr().cast::(); + // SAFETY: `BytesRef` guarantees alignment and that the buffer covers + // at least the fixed header size. let hdr = unsafe { &*ptr }; - let payload_len = hdr.payload_len(); - let total_size = size_of::() + payload_len; + let total_size = hdr.total_size(); + let header_size = size_of::(); + if total_size < header_size { + return Err(MemoryError::SizeInsufficient(total_size, header_size)); + } if total_size > bytes.len() { return Err(MemoryError::InvalidReportedTotalSize( total_size, bytes.len(), )); } + let payload_len = total_size - header_size; // At this point we know that the memory slice fulfills the base // assumptions and requirements. Now, we safety can create the fat @@ -349,6 +357,8 @@ impl DynSizedStructure { let dst_size = payload_len; // Create fat pointer for the DST. let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size); + // SAFETY: The allocation was sized from the validated reported total + // size, so the fat pointer refers to initialized memory. let reference = unsafe { &*ptr }; Ok(reference) } @@ -368,9 +378,18 @@ impl DynSizedStructure { /// The caller must ensure that the function operates on valid memory. pub unsafe fn ref_from_ptr<'a>(ptr: NonNull) -> Result<&'a Self, MemoryError> { let ptr = ptr.as_ptr().cast_const(); + // SAFETY: `ptr` came from a valid pointer to the header; we only read + // the reported total size and immediately re-slice that range. let hdr = unsafe { &*ptr }; + let total_size = hdr.total_size(); + let header_size = size_of::(); + if total_size < header_size { + return Err(MemoryError::SizeInsufficient(total_size, header_size)); + } - let slice = unsafe { slice::from_raw_parts(ptr.cast::(), hdr.total_size()) }; + // SAFETY: `total_size` came from the validated header and matches the + // readable byte range for the structure. + let slice = unsafe { slice::from_raw_parts(ptr.cast::(), total_size) }; Self::ref_from_slice(slice) } @@ -405,10 +424,13 @@ impl DynSizedStructure { /// # Panics /// This panics if there is a size mismatch. However, this should never be /// the case if all types follow their documented requirements. - pub fn cast + ?Sized>(&self) -> &T { + pub fn cast + ?Sized>(&self) -> &T + where + T::Metadata: Default, + { // Thin or fat pointer, depending on type. // However, only thin ptr is needed. - let base_ptr = ptr::addr_of!(*self); + let base_ptr = &raw const *self; // This should be a compile-time assertion. However, this is the best // location to place it for now. @@ -417,6 +439,8 @@ impl DynSizedStructure { let t_dst_size = T::dst_len(self.header()); // Creates thin or fat pointer, depending on type. let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size); + // SAFETY: `self` is a valid reference and the cast keeps the same + // allocation; `T::dst_len` determines the matching tail length. let t_ref = unsafe { &*t_ptr }; assert_eq!(size_of_val(self), size_of_val(t_ref)); @@ -456,7 +480,7 @@ pub fn validate_tag_sequence( let tag = &bytes[offset..]; let total_size = - u32::from_ne_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize; + u32::from_le_bytes(tag[4..8].try_into().expect("slice has exactly 4 bytes")) as usize; if total_size < TAG_HEADER_SIZE { return Err(MemoryError::SizeInsufficient(total_size, TAG_HEADER_SIZE)); @@ -617,4 +641,24 @@ mod tests { Err(MemoryError::InvalidReportedTotalSize(24, 16)) ); } + + #[test] + fn test_ref_from_slice_rejects_too_small_reported_size() { + #[rustfmt::skip] + let bytes = AlignedBytes::new( + [ + 0x37, 0x13, 0, 0, + /* Tag size */ + 4, 0, 0, 0, + /* Remaining bytes are irrelevant. */ + 0, 1, 2, 3, + 0, 0, 0, 0, + ], + ); + + assert_eq!( + DynSizedStructure::::ref_from_slice(bytes.borrow()), + Err(MemoryError::SizeInsufficient(4, 8)) + ); + } } diff --git a/multiboot2-common/src/tag.rs b/multiboot2-common/src/tag.rs index 6e3947da..6f21a4a3 100644 --- a/multiboot2-common/src/tag.rs +++ b/multiboot2-common/src/tag.rs @@ -9,7 +9,10 @@ use ptr_meta::Pointee; /// [`DynSizedStructure::cast`]. /// /// Structs that are a DST must provide a **correct** [`MaybeDynSized::dst_len`] -/// implementation. +/// implementation. The needed metadata type is either `()` for sized types or +/// `usize` for dynamically sized types. For sized types, there is a default +/// implementation. Only dynamically sized types need to implement +/// [`MaybeDynSized::dst_len`]. /// /// # ABI /// Implementors **must** use `#[repr(C)]`. As there might be padding necessary @@ -43,11 +46,20 @@ pub trait MaybeDynSized: Pointee { /// /// For sized tags, this just returns `()`. For DSTs, this returns an /// `usize`. - fn dst_len(header: &Self::Header) -> Self::Metadata; + fn dst_len(header: &Self::Header) -> Self::Metadata + where + // Either `()` or `usize`, never something else + Self::Metadata: Default, + { + let _ = header; + Default::default() + } /// Returns the corresponding [`Header`]. fn header(&self) -> &Self::Header { - let ptr = core::ptr::addr_of!(*self); + let ptr = &raw const *self; + // SAFETY: `self` is a valid reference and `Self::Header` is the + // prefix of this `repr(C)` structure at the same address. unsafe { &*ptr.cast::() } } @@ -62,9 +74,11 @@ pub trait MaybeDynSized: Pointee { /// [`BytesRef`]. This includes padding bytes. To only get the "true" tag /// data, read the tag size from [`Self::header`] and create a sub slice. fn as_bytes(&self) -> BytesRef<'_, Self::Header> { - let ptr = core::ptr::addr_of!(*self); + let ptr = &raw const *self; // Actual tag size with optional terminating padding. let size = size_of_val(self); + // SAFETY: `ptr` points to `self`'s allocation and `size_of_val(self)` + // covers the initialized object representation, including padding. let slice = unsafe { slice::from_raw_parts(ptr.cast::(), size) }; // Unwrap is fine as this type can't exist without the underlying memory // guarantees. diff --git a/multiboot2-common/src/test_utils.rs b/multiboot2-common/src/test_utils.rs index 4f0f74ca..c534eefc 100644 --- a/multiboot2-common/src/test_utils.rs +++ b/multiboot2-common/src/test_utils.rs @@ -69,8 +69,8 @@ impl DummyTestHeader { } impl Header for DummyTestHeader { - fn payload_len(&self) -> usize { - self.size as usize - size_of::() + fn total_size(&self) -> usize { + self.size as usize } fn set_size(&mut self, total_size: usize) { @@ -116,8 +116,6 @@ impl Tag for DummyDstTag { #[cfg(test)] mod tests { - use core::ptr::addr_of; - use crate::ALIGNMENT; use super::*; @@ -128,17 +126,17 @@ mod tests { let bytes = AlignedBytes([0]); assert_eq!(bytes.as_ptr().align_offset(8), 0); - assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); + assert_eq!((&raw const bytes[0]).align_offset(8), 0); let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7]); assert_eq!(bytes.as_ptr().align_offset(8), 0); - assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); + assert_eq!((&raw const bytes[0]).align_offset(8), 0); let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); assert_eq!(bytes.as_ptr().align_offset(8), 0); - assert_eq!((addr_of!(bytes[0])).align_offset(8), 0); - assert_eq!((addr_of!(bytes[7])).align_offset(8), 1); - assert_eq!((addr_of!(bytes[8])).align_offset(8), 0); - assert_eq!((addr_of!(bytes[9])).align_offset(8), 7); + assert_eq!((&raw const bytes[0]).align_offset(8), 0); + assert_eq!((&raw const bytes[7]).align_offset(8), 1); + assert_eq!((&raw const bytes[8]).align_offset(8), 0); + assert_eq!((&raw const bytes[9]).align_offset(8), 7); } } diff --git a/multiboot2-header/src/address.rs b/multiboot2-header/src/address.rs index 7cd33c43..5b9b422b 100644 --- a/multiboot2-header/src/address.rs +++ b/multiboot2-header/src/address.rs @@ -90,8 +90,6 @@ impl MaybeDynSized for AddressHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for AddressHeaderTag { diff --git a/multiboot2-header/src/builder.rs b/multiboot2-header/src/builder.rs index 0037c700..324f0467 100644 --- a/multiboot2-header/src/builder.rs +++ b/multiboot2-header/src/builder.rs @@ -215,9 +215,13 @@ mod tests { )); let structure = builder.build(); - let header = + + let header = { + // SAFETY: The builder emits a fully formed, aligned header + // buffer with a valid end tag. unsafe { Multiboot2Header::load(structure.as_bytes().as_ref().as_ptr().cast()) } - .unwrap(); + .unwrap() + }; assert_eq!(header.verify_checksum(), Ok(())); assert_eq!( diff --git a/multiboot2-header/src/console.rs b/multiboot2-header/src/console.rs index df65d846..3f649420 100644 --- a/multiboot2-header/src/console.rs +++ b/multiboot2-header/src/console.rs @@ -61,8 +61,6 @@ impl MaybeDynSized for ConsoleHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::() + size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for ConsoleHeaderTag { diff --git a/multiboot2-header/src/end.rs b/multiboot2-header/src/end.rs index 49620d00..42c1f7e5 100644 --- a/multiboot2-header/src/end.rs +++ b/multiboot2-header/src/end.rs @@ -3,7 +3,7 @@ use multiboot2_common::{MaybeDynSized, Tag}; /// Terminates a list of optional tags in a Multiboot2 header. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct EndHeaderTag { header: HeaderTagHeader, } @@ -49,8 +49,6 @@ impl MaybeDynSized for EndHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for EndHeaderTag { diff --git a/multiboot2-header/src/entry_efi_32.rs b/multiboot2-header/src/entry_efi_32.rs index 66ddb8d1..e808179a 100644 --- a/multiboot2-header/src/entry_efi_32.rs +++ b/multiboot2-header/src/entry_efi_32.rs @@ -70,8 +70,6 @@ impl MaybeDynSized for EntryEfi32HeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::() + size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for EntryEfi32HeaderTag { diff --git a/multiboot2-header/src/entry_efi_64.rs b/multiboot2-header/src/entry_efi_64.rs index 9c70b555..856a5d48 100644 --- a/multiboot2-header/src/entry_efi_64.rs +++ b/multiboot2-header/src/entry_efi_64.rs @@ -70,8 +70,6 @@ impl MaybeDynSized for EntryEfi64HeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::() + size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for EntryEfi64HeaderTag { diff --git a/multiboot2-header/src/framebuffer.rs b/multiboot2-header/src/framebuffer.rs index 6a1af645..b9091947 100644 --- a/multiboot2-header/src/framebuffer.rs +++ b/multiboot2-header/src/framebuffer.rs @@ -69,8 +69,6 @@ impl MaybeDynSized for FramebufferHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::() + 3 * size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for FramebufferHeaderTag { diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index aca26837..11d326cc 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -50,6 +50,8 @@ impl<'a> Multiboot2Header<'a> { /// program may observe unsynchronized mutation. pub unsafe fn load(ptr: *const Multiboot2BasicHeader) -> Result { let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?; + // SAFETY: `ptr` was checked for null and the DST constructor + // validates size and layout. let inner = unsafe { DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)? }; let this = Self(inner); @@ -69,9 +71,9 @@ impl<'a> Multiboot2Header<'a> { /// Checks whether the header has a valid, complete tag sequence. fn has_valid_tag_sequence(&self) -> Result { validate_tag_sequence(self.0.payload(), |tag| { - let typ = u16::from_ne_bytes(tag[0..2].try_into().unwrap()); - let flags = u16::from_ne_bytes(tag[2..4].try_into().unwrap()); - let size = u32::from_ne_bytes(tag[4..8].try_into().unwrap()) as usize; + let typ = u16::from_le_bytes(tag[0..2].try_into().unwrap()); + let flags = u16::from_le_bytes(tag[2..4].try_into().unwrap()); + let size = u32::from_le_bytes(tag[4..8].try_into().unwrap()) as usize; typ == HeaderTagType::End as u16 && flags == crate::HeaderTagFlag::Required as u16 @@ -163,6 +165,8 @@ impl<'a> Multiboot2Header<'a> { .wrapping_add(magic_begin_idx) .cast::(); + // SAFETY: `ptr` points into `buffer`, which has been checked + // for alignment and bounds. let header = unsafe { Self::load(ptr)? }; Ok((header, magic_begin_idx)) } @@ -170,7 +174,9 @@ impl<'a> Multiboot2Header<'a> { /// Returns a [`TagIter`]. #[must_use] pub fn iter(&self) -> TagIter<'_> { - TagIter::new(self.0.payload()) + // SAFETY: `load()` validated the tag chain, and the iterator + // only walks that validated payload. + unsafe { TagIter::new(self.0.payload()) } } /// Wrapper around [`Multiboot2BasicHeader::verify_checksum`]. @@ -276,7 +282,10 @@ impl<'a> Multiboot2Header<'a> { #[must_use] fn get_tag + ?Sized + 'a>( &'a self, - ) -> Option<&'a T> { + ) -> Option<&'a T> + where + T::Metadata: Default, + { self.iter() .find(|tag| tag.header().typ() == T::ID) .map(|tag| tag.cast::()) @@ -393,9 +402,8 @@ impl Multiboot2BasicHeader { } impl Header for Multiboot2BasicHeader { - fn payload_len(&self) -> usize { - assert!(self.length as usize >= size_of::()); - self.length as usize - size_of::() + fn total_size(&self) -> usize { + self.length as usize } fn set_size(&mut self, total_size: usize) { @@ -498,6 +506,8 @@ mod tests { let mut bytes = AlignedBytes::new([0; 24]); write_minimal_valid_header_tag(&mut bytes.0); + // SAFETY: The test buffer is aligned and contains a valid + // header layout. let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) }; assert!(header.is_ok()); @@ -511,6 +521,8 @@ mod tests { bytes.0[8..12].copy_from_slice(&16_u32.to_le_bytes()); bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes()); + // SAFETY: The test buffer is aligned and contains a valid + // header layout. let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) }; assert!(matches!(header, Err(LoadError::NoEndTag))); @@ -527,6 +539,8 @@ mod tests { bytes.0[16..18].copy_from_slice(&(HeaderTagType::InformationRequest as u16).to_le_bytes()); bytes.0[20..24].copy_from_slice(&24_u32.to_le_bytes()); + // SAFETY: The test buffer is aligned and contains a valid + // header layout. let header = unsafe { Multiboot2Header::load(bytes.as_ptr().cast()) }; assert_eq!( diff --git a/multiboot2-header/src/information_request.rs b/multiboot2-header/src/information_request.rs index 2e5f9e20..03c091d8 100644 --- a/multiboot2-header/src/information_request.rs +++ b/multiboot2-header/src/information_request.rs @@ -6,10 +6,7 @@ use core::fmt::{Debug, Formatter}; use multiboot2_common::new_boxed; use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use { - alloc::boxed::Box, - core::{ptr, slice}, -}; +use {alloc::boxed::Box, core::slice}; /// Specifies what specific tag types the bootloader should provide /// inside the mbi. @@ -26,8 +23,9 @@ impl InformationRequestHeaderTag { #[must_use] pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeId]) -> Box { let header = HeaderTagHeader::new(HeaderTagType::InformationRequest, flags, 0); + // SAFETY: The memory we are using is valid. let requests = unsafe { - let ptr = ptr::addr_of!(*requests); + let ptr = &raw const *requests; slice::from_raw_parts(ptr.cast::(), size_of_val(requests)) }; new_boxed(header, &[requests]) diff --git a/multiboot2-header/src/lib.rs b/multiboot2-header/src/lib.rs index 7ed1d60e..8ca54871 100644 --- a/multiboot2-header/src/lib.rs +++ b/multiboot2-header/src/lib.rs @@ -31,6 +31,7 @@ clippy::all, clippy::cargo, clippy::nursery, + clippy::undocumented_unsafe_blocks, clippy::must_use_candidate, // clippy::restriction, // clippy::pedantic diff --git a/multiboot2-header/src/module_align.rs b/multiboot2-header/src/module_align.rs index 8714c8d9..2055d7b7 100644 --- a/multiboot2-header/src/module_align.rs +++ b/multiboot2-header/src/module_align.rs @@ -40,8 +40,6 @@ impl MaybeDynSized for ModuleAlignHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for ModuleAlignHeaderTag { diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index 3bac9119..3eaa059d 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -116,8 +116,6 @@ impl MaybeDynSized for RelocatableHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for RelocatableHeaderTag { diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index c56c3a6a..77cf2a32 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -71,12 +71,12 @@ pub enum HeaderTagFlag { /// The common header that all header tags share. Specific tags may have /// additional fields that depend on the `typ` and the `size` field. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] +#[repr(C, align(8))] pub struct HeaderTagHeader { typ: HeaderTagType, /* u16 */ flags: HeaderTagFlag, /* u16 */ size: u32, - // Followed by optional additional tag specific fields. + // Followed by optional additional tag-specific fields. } impl HeaderTagHeader { @@ -106,8 +106,8 @@ impl HeaderTagHeader { } impl Header for HeaderTagHeader { - fn payload_len(&self) -> usize { - self.size as usize - size_of::() + fn total_size(&self) -> usize { + self.size as usize } fn set_size(&mut self, total_size: usize) { diff --git a/multiboot2-header/src/uefi_bs.rs b/multiboot2-header/src/uefi_bs.rs index c69b9f10..241f62a9 100644 --- a/multiboot2-header/src/uefi_bs.rs +++ b/multiboot2-header/src/uefi_bs.rs @@ -40,8 +40,6 @@ impl MaybeDynSized for EfiBootServiceHeaderTag { type Header = HeaderTagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_header: &Self::Header) -> Self::Metadata {} } impl Tag for EfiBootServiceHeaderTag { diff --git a/multiboot2/CHANGELOG.md b/multiboot2/CHANGELOG.md index 6dad733c..9933cc6e 100644 --- a/multiboot2/CHANGELOG.md +++ b/multiboot2/CHANGELOG.md @@ -13,6 +13,8 @@ - Added UserDefined section to `ElfSectionType`. - Added equality implementations for `BootInformation`. - Fixed `BootInformation::load` to validate the complete padded tag sequence. +- Fixed RSDP tag constructors to compute their checksums automatically and + tightened checksum validation to avoid reading past malformed lengths. - Fixed indexed framebuffer parsing to reject palette metadata that exceeds the tag payload. - Fixed EFI memory map parsing to reject descriptor sizes that cannot safely diff --git a/multiboot2/src/apm.rs b/multiboot2/src/apm.rs index bcc73dcc..70a438b7 100644 --- a/multiboot2/src/apm.rs +++ b/multiboot2/src/apm.rs @@ -114,8 +114,6 @@ impl MaybeDynSized for ApmTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for ApmTag { diff --git a/multiboot2/src/boot_information.rs b/multiboot2/src/boot_information.rs index 18074334..1ab8fbf1 100644 --- a/multiboot2/src/boot_information.rs +++ b/multiboot2/src/boot_information.rs @@ -56,9 +56,8 @@ impl BootInformationHeader { } impl Header for BootInformationHeader { - fn payload_len(&self) -> usize { - assert!(self.total_size as usize >= size_of::()); - self.total_size as usize - size_of::() + fn total_size(&self) -> usize { + self.total_size as usize } fn set_size(&mut self, total_size: usize) { @@ -104,6 +103,8 @@ impl<'a> BootInformation<'a> { /// program may observe unsynchronized mutation. pub unsafe fn load(ptr: *const BootInformationHeader) -> Result { let ptr = NonNull::new(ptr.cast_mut()).ok_or(LoadError::Memory(MemoryError::Null))?; + // SAFETY: `ptr` was checked for null and `ref_from_ptr` validates the + // reported total size before constructing the DST reference. let inner = unsafe { DynSizedStructure::ref_from_ptr(ptr).map_err(LoadError::Memory)? }; let this = Self(inner); @@ -116,8 +117,8 @@ impl<'a> BootInformation<'a> { /// Checks if the MBI has a valid, complete tag sequence. fn has_valid_tag_sequence(&self) -> Result { validate_tag_sequence(self.0.payload(), |tag| { - let typ = u32::from_ne_bytes(tag[0..4].try_into().unwrap()); - let size = u32::from_ne_bytes(tag[4..8].try_into().unwrap()) as usize; + let typ = u32::from_le_bytes(tag[0..4].try_into().unwrap()); + let size = u32::from_le_bytes(tag[4..8].try_into().unwrap()) as usize; typ == TagType::End.val() && size == size_of::() }) @@ -133,7 +134,7 @@ impl<'a> BootInformation<'a> { /// Get the start address of the boot info as pointer. #[must_use] pub const fn as_ptr(&self) -> *const () { - core::ptr::addr_of!(*self.0).cast() + (&raw const *self.0).cast() } /// Get the end address of the boot info. @@ -402,7 +403,10 @@ impl<'a> BootInformation<'a> { #[must_use] pub fn get_tag + ?Sized + 'a>( &'a self, - ) -> Option<&'a T> { + ) -> Option<&'a T> + where + T::Metadata: Default, + { self.tags() .find(|tag| tag.header().typ == T::ID) .map(|tag| tag.cast::()) @@ -415,7 +419,8 @@ impl<'a> BootInformation<'a> { /// tag getters as normal bootloaders provide most tags only once. #[must_use] pub fn tags(&self) -> TagIter<'_> { - TagIter::new(self.0.payload()) + // SAFETY: We validated the chain of tags beforehand. + unsafe { TagIter::new(self.0.payload()) } } } diff --git a/multiboot2/src/bootdev.rs b/multiboot2/src/bootdev.rs index 75a705a6..9e2cf5e2 100644 --- a/multiboot2/src/bootdev.rs +++ b/multiboot2/src/bootdev.rs @@ -55,8 +55,6 @@ impl MaybeDynSized for BootdevTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for BootdevTag { diff --git a/multiboot2/src/builder.rs b/multiboot2/src/builder.rs index bdf61c99..51efa95e 100644 --- a/multiboot2/src/builder.rs +++ b/multiboot2/src/builder.rs @@ -53,7 +53,7 @@ impl Builder { Self { cmdline: None, bootloader: None, - modules: vec![], + modules: alloc::vec![], meminfo: None, bootdev: None, mmap: None, @@ -63,7 +63,7 @@ impl Builder { apm: None, efi32: None, efi64: None, - smbios: vec![], + smbios: alloc::vec![], rsdpv1: None, rsdpv2: None, efi_mmap: None, @@ -72,7 +72,7 @@ impl Builder { efi32_ih: None, efi64_ih: None, image_load_addr: None, - custom_tags: vec![], + custom_tags: alloc::vec![], } } @@ -354,8 +354,8 @@ mod tests { .efi64(EFISdt64Tag::new(0x1000)) .add_smbios(SmbiosTag::new(0, 0, &[1, 2, 3])) .add_smbios(SmbiosTag::new(1, 1, &[4, 5, 6])) - .rsdpv1(RsdpV1Tag::new(0, *b"abcdef", 5, 6)) - .rsdpv2(RsdpV2Tag::new(0, *b"abcdef", 5, 6, 5, 4, 7)) + .rsdpv1(RsdpV1Tag::new(*b"abcdef", 5, 6)) + .rsdpv2(RsdpV2Tag::new(*b"abcdef", 5, 6, 5, 4)) .efi_mmap(EFIMemoryMapTag::new_from_descs(&[ MemoryDescriptor::default(), MemoryDescriptor::default(), @@ -372,6 +372,8 @@ mod tests { let structure = builder.build(); + // SAFETY: The builder constructs a complete, aligned MBI with + // a valid end tag. let info = unsafe { BootInformation::load(structure.as_bytes().as_ptr().cast()) }.unwrap(); for tag in info.tags() { // Mainly a test for Miri. diff --git a/multiboot2/src/efi.rs b/multiboot2/src/efi.rs index 5320534d..287c8cef 100644 --- a/multiboot2/src/efi.rs +++ b/multiboot2/src/efi.rs @@ -41,8 +41,6 @@ impl MaybeDynSized for EFISdt32Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EFISdt32Tag { @@ -80,8 +78,6 @@ impl MaybeDynSized for EFISdt64Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EFISdt64Tag { @@ -122,8 +118,6 @@ impl MaybeDynSized for EFIImageHandle32Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EFIImageHandle32Tag { @@ -162,8 +156,6 @@ impl MaybeDynSized for EFIImageHandle64Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EFIImageHandle64Tag { @@ -200,8 +192,6 @@ impl MaybeDynSized for EFIBootServicesNotExitedTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EFIBootServicesNotExitedTag { diff --git a/multiboot2/src/end.rs b/multiboot2/src/end.rs index e4bdd5cb..a9bb7faa 100644 --- a/multiboot2/src/end.rs +++ b/multiboot2/src/end.rs @@ -22,8 +22,6 @@ impl MaybeDynSized for EndTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for EndTag { @@ -40,6 +38,8 @@ mod tests { #[test] /// Compile time test for [`EndTag`]. fn test_end_tag_size() { + // SAFETY: `EndTag` is a plain `repr(C)` POD with the exact + // eight-byte end-tag layout. unsafe { transmute::<[u8; 8], EndTag>([0u8; 8]); } diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index 4d5eca46..9c4d4ff7 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -52,7 +52,7 @@ impl<'a> Reader<'a> { } const fn current_ptr(&self) -> *const u8 { - unsafe { self.buffer.as_ptr().add(self.off) } + self.buffer.as_ptr().wrapping_add(self.off) } } @@ -187,7 +187,7 @@ impl FramebufferTag { self.buffer.len() - reader.off >= palette_len, "indexed framebuffer palette must fit in the tag" ); - + // SAFETY: The memory we are using is valid. unsafe { slice::from_raw_parts( reader.current_ptr().cast::(), diff --git a/multiboot2/src/image_load_addr.rs b/multiboot2/src/image_load_addr.rs index 5f464428..67fda150 100644 --- a/multiboot2/src/image_load_addr.rs +++ b/multiboot2/src/image_load_addr.rs @@ -36,8 +36,6 @@ impl MaybeDynSized for ImageLoadPhysAddrTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for ImageLoadPhysAddrTag { diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index 0004114b..908d15ad 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -4,6 +4,7 @@ clippy::all, clippy::cargo, clippy::nursery, + clippy::undocumented_unsafe_blocks, clippy::must_use_candidate, // clippy::restriction, // clippy::pedantic @@ -43,7 +44,6 @@ //! ## MSRV //! The MSRV is 1.85.1 stable. -#[cfg_attr(feature = "builder", macro_use)] #[cfg(feature = "builder")] extern crate alloc; @@ -145,6 +145,8 @@ mod tests { 8, 0, 0, 0, // end tag size ]); let ptr = bytes.0.as_ptr(); + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); @@ -162,6 +164,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -183,6 +187,8 @@ mod tests { 8, 0, 0, // end tag size ]); let ptr = bytes.0.as_ptr(); + // SAFETY: The buffer is aligned; the invalidity is in its + // contents. let bi = unsafe { BootInformation::load(ptr.cast()) }; assert_eq!(bi, Err(LoadError::Memory(MemoryError::MissingPadding))); @@ -197,6 +203,8 @@ mod tests { 9, 0, 0, 0, // end tag size ]); let ptr = bytes.0.as_ptr(); + // SAFETY: The buffer is aligned; the invalidity is in its + // contents. let bi = unsafe { BootInformation::load(ptr.cast()) }; assert_eq!( @@ -218,6 +226,8 @@ mod tests { 0, 0, 0, 0, // tag data ]); let ptr = bytes.0.as_ptr(); + // SAFETY: The buffer is aligned; the invalidity is in its + // contents. let bi = unsafe { BootInformation::load(ptr.cast()) }; assert_eq!( @@ -242,6 +252,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -283,6 +295,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a valid synthetic + // MBI. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -347,6 +361,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a valid synthetic + // MBI. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -462,6 +478,8 @@ mod tests { let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a valid synthetic + // MBI. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -553,6 +571,7 @@ mod tests { #[test] /// Compile time test for [`VBEInfoTag`]. fn vbe_info_tag_size() { + // SAFETY: The test only checks the fixed-size ABI layout. unsafe { // 16 for the start + 512 from `VBEControlInfo` + 256 from `VBEModeInfo`. transmute::<[u8; 784], VBEInfoTag>([0u8; 784]); @@ -819,6 +838,8 @@ mod tests { } let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); test_grub2_boot_info(&bi, addr, string_addr, &bytes.0, &string_bytes.0); @@ -1074,6 +1095,8 @@ mod tests { } let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); @@ -1144,6 +1167,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -1177,6 +1202,8 @@ mod tests { 0, 0, 0, 0, // end tag type. 8, 0, 0, 0, // end tag size. ]); + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(bytes2.as_ptr().cast()) }; let bi = bi.unwrap(); let efi_mmap = bi.efi_memory_map_tag(); @@ -1204,8 +1231,6 @@ mod tests { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) -> Self::Metadata {} } impl Tag for CustomTag { @@ -1251,6 +1276,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -1334,6 +1361,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); let addr = ptr as usize; + // SAFETY: The buffer is aligned and contains a complete + // synthetic MBI with a valid end tag. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); assert_eq!(addr, bi.start_address()); @@ -1383,6 +1412,8 @@ mod tests { ]); let ptr = bytes.0.as_ptr(); + // SAFETY: The buffer is aligned and contains a valid synthetic + // MBI. let bi = unsafe { BootInformation::load(ptr.cast()) }; let bi = bi.unwrap(); diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index 98233078..f320e659 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -43,6 +43,8 @@ impl MemoryMapTag { let areas = { let ptr = areas.as_ptr().cast::(); let len = size_of_val(areas); + // SAFETY: `areas` is a live slice; we only reinterpret its + // initialized bytes. unsafe { slice::from_raw_parts(ptr, len) } }; new_boxed(header, &[&entry_size, &entry_version, areas]) @@ -293,8 +295,6 @@ impl MaybeDynSized for BasicMemoryInfoTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for BasicMemoryInfoTag { @@ -336,6 +336,8 @@ impl EFIMemoryMapTag { let efi_mmap = { let ptr = descs.as_ptr().cast::(); let len = size_of_val(descs); + // SAFETY: `descs` is a live slice; we only reinterpret its + // initialized bytes. unsafe { slice::from_raw_parts(ptr, len) } }; @@ -453,14 +455,16 @@ impl<'a> Iterator for EFIMemoryAreaIter<'a> { return None; } - let desc = unsafe { - self.mmap_tag + let desc = { + let ptr = self + .mmap_tag .memory_map .as_ptr() - .add(self.i * self.mmap_tag.desc_size as usize) - .cast::() - .as_ref() - .unwrap() + .wrapping_add(self.i * self.mmap_tag.desc_size as usize) + .cast::(); + // SAFETY: The enclosing tag bounds the iterator, and `ptr` + // points into it. + unsafe { ptr.as_ref().unwrap() } }; self.i += 1; @@ -562,8 +566,10 @@ mod tests { 10, 8433664, 0, 1, 15, 0, 7, 8437760, 0, 4, 15, 0, 10, 8454144, 0, 240, 15, 0, ]; let buf = MMAP_RAW; + // SAFETY: `buf` is a plain array; we only reinterpret its + // initialized bytes. let buf = unsafe { - core::slice::from_raw_parts(buf.as_ptr().cast::(), buf.len() * size_of::()) + slice::from_raw_parts(buf.as_ptr().cast::(), buf.len() * size_of::()) }; let tag = EFIMemoryMapTag::new_from_map(DESC_SIZE, DESC_VERSION, buf); let entries = tag.memory_areas().copied().collect::>(); diff --git a/multiboot2/src/rsdp.rs b/multiboot2/src/rsdp.rs index 2542bc51..da9ce357 100644 --- a/multiboot2/src/rsdp.rs +++ b/multiboot2/src/rsdp.rs @@ -19,7 +19,16 @@ use core::str; use core::str::Utf8Error; use multiboot2_common::{MaybeDynSized, Tag}; -const RSDPV1_LENGTH: usize = 20; +fn sum_bytes(parts: &[&[u8]]) -> u8 { + parts + .iter() + .flat_map(|part| part.iter().copied()) + .fold(0u8, |acc, val| acc.wrapping_add(val)) +} + +fn compute_checksum(bytes: &[&[u8]]) -> u8 { + 0u8.wrapping_sub(sum_bytes(bytes)) +} /// This tag contains a copy of RSDP as defined per ACPI 1.0 specification. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -30,7 +39,8 @@ pub struct RsdpV1Tag { checksum: u8, oem_id: [u8; 6], revision: u8, - rsdt_address: u32, // This is the PHYSICAL address of the RSDT + // This is the PHYSICAL address of the RSDT + rsdt_address: u32, } impl RsdpV1Tag { @@ -41,7 +51,17 @@ impl RsdpV1Tag { /// Constructs a new tag. #[must_use] - pub fn new(checksum: u8, oem_id: [u8; 6], revision: u8, rsdt_address: u32) -> Self { + pub fn new(oem_id: [u8; 6], revision: u8, rsdt_address: u32) -> Self { + let rsdt_address_bytes = rsdt_address.to_le_bytes(); + let checksum_seed = [0u8]; + let revision_bytes = [revision]; + let checksum = compute_checksum(&[ + Self::SIGNATURE.as_slice(), + checksum_seed.as_slice(), + oem_id.as_slice(), + revision_bytes.as_slice(), + rsdt_address_bytes.as_slice(), + ]); Self { header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), signature: Self::SIGNATURE, @@ -59,15 +79,18 @@ impl RsdpV1Tag { str::from_utf8(&self.signature) } - /// Validation of the RSDPv1 checksum + /// Validation of the RSDPv1 checksum. #[must_use] pub fn checksum_is_valid(&self) -> bool { - let bytes = - unsafe { slice::from_raw_parts(self as *const _ as *const u8, RSDPV1_LENGTH + 8) }; - bytes[8..] - .iter() - .fold(0u8, |acc, val| acc.wrapping_add(*val)) - == 0 + let rsdp_ptr = (self as *const Self) + .cast::() + // Skip header + .wrapping_add(size_of::()); + let rsdp_len = Self::BASE_SIZE - size_of::(); + // SAFETY: `self` is a valid reference, and we only read the + // initialized raw representation of the fixed-size RSDP payload. + let bytes = unsafe { slice::from_raw_parts(rsdp_ptr, rsdp_len) }; + sum_bytes(&[bytes]) == 0 } /// An OEM-supplied string that identifies the OEM. @@ -92,8 +115,6 @@ impl MaybeDynSized for RsdpV1Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for RsdpV1Tag { @@ -113,8 +134,8 @@ pub struct RsdpV2Tag { revision: u8, rsdt_address: u32, length: u32, - xsdt_address: u64, // This is the PHYSICAL address of the XSDT + xsdt_address: u64, ext_checksum: u8, _reserved: [u8; 3], } @@ -129,14 +150,37 @@ impl RsdpV2Tag { /// Constructs a new tag. #[must_use] pub fn new( - checksum: u8, oem_id: [u8; 6], revision: u8, rsdt_address: u32, length: u32, xsdt_address: u64, - ext_checksum: u8, ) -> Self { + let rsdt_address_bytes = rsdt_address.to_le_bytes(); + let length_bytes = length.to_le_bytes(); + let xsdt_address_bytes = xsdt_address.to_le_bytes(); + let checksum_seed = [0u8]; + let ext_checksum_seed = [0u8]; + let reserved = [0u8; 3]; + let revision_bytes = [revision]; + let checksum = compute_checksum(&[ + Self::SIGNATURE.as_slice(), + checksum_seed.as_slice(), + oem_id.as_slice(), + revision_bytes.as_slice(), + rsdt_address_bytes.as_slice(), + ]); + let ext_checksum = compute_checksum(&[ + Self::SIGNATURE.as_slice(), + core::slice::from_ref(&checksum), + oem_id.as_slice(), + revision_bytes.as_slice(), + rsdt_address_bytes.as_slice(), + length_bytes.as_slice(), + xsdt_address_bytes.as_slice(), + ext_checksum_seed.as_slice(), + reserved.as_slice(), + ]); Self { header: TagHeader::new(Self::ID, Self::BASE_SIZE as u32), signature: Self::SIGNATURE, @@ -158,16 +202,23 @@ impl RsdpV2Tag { str::from_utf8(&self.signature) } - /// Validation of the RSDPv2 extended checksum + /// Validation of the RSDPv2 extended checksum. #[must_use] pub fn checksum_is_valid(&self) -> bool { - let bytes = unsafe { - slice::from_raw_parts(self as *const _ as *const u8, self.length as usize + 8) - }; - bytes[8..] - .iter() - .fold(0u8, |acc, val| acc.wrapping_add(*val)) - == 0 + // SAFETY: `self` is a valid reference, and we only read the + // initialized raw representation of the fixed-size layout. + let bytes = + unsafe { slice::from_raw_parts((self as *const Self).cast::(), size_of::()) }; + let length = self.length as usize; + if length != Self::BASE_SIZE - size_of::() { + return false; + } + let ext_end = size_of::() + length; + if ext_end > bytes.len() { + return false; + } + + sum_bytes(&[&bytes[8..28]]) == 0 && sum_bytes(&[&bytes[8..ext_end]]) == 0 } /// An OEM-supplied string that identifies the OEM. @@ -200,8 +251,6 @@ impl MaybeDynSized for RsdpV2Tag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for RsdpV2Tag { @@ -209,3 +258,41 @@ impl Tag for RsdpV2Tag { const ID: TagType = TagType::AcpiV2; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn v1_new_computes_valid_checksum() { + let tag = RsdpV1Tag::new(*b"ABCDEF", 1, 0x1234_5678); + assert!(tag.checksum_is_valid()); + } + + #[test] + fn v2_new_computes_valid_checksums() { + let tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0); + assert!(tag.checksum_is_valid()); + } + + #[test] + fn v2_checksum_validation_rejects_corruption() { + let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0); + tag.ext_checksum ^= 1; + assert!(!tag.checksum_is_valid()); + } + + #[test] + fn v2_checksum_validation_rejects_checksum_corruption() { + let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0); + tag.checksum ^= 1; + assert!(!tag.checksum_is_valid()); + } + + #[test] + fn v2_checksum_validation_rejects_invalid_length() { + let mut tag = RsdpV2Tag::new(*b"ABCDEF", 2, 0x1234_5678, 36, 0x1234_5678_9abc_def0); + tag.length = 0; + assert!(!tag.checksum_is_valid()); + } +} diff --git a/multiboot2/src/tag.rs b/multiboot2/src/tag.rs index 239b8564..574be7b2 100644 --- a/multiboot2/src/tag.rs +++ b/multiboot2/src/tag.rs @@ -11,7 +11,7 @@ use multiboot2_common::Header; /// /// It is the sized counterpart of `GenericTag`, an internal type. #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[repr(C, align(8))] // Alignment also propagates to all tag types using this. +#[repr(C, align(8))] pub struct TagHeader { /// The ABI-compatible [`TagType`]. /// @@ -33,9 +33,8 @@ impl TagHeader { } impl Header for TagHeader { - fn payload_len(&self) -> usize { - assert!(self.size as usize >= size_of::()); - self.size as usize - size_of::() + fn total_size(&self) -> usize { + self.size as usize } fn set_size(&mut self, total_size: usize) { diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index 1677382c..2d22d0ba 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -86,8 +86,6 @@ impl MaybeDynSized for VBEInfoTag { type Header = TagHeader; const BASE_SIZE: usize = size_of::(); - - fn dst_len(_: &TagHeader) {} } impl Tag for VBEInfoTag {