瀏覽代碼

multiboot2: massive internal refactoring + most tests pass Miri 🎉

The goal of this commit is to prepare 100% memory safety (using Miri
passing tests as reference). For that, we need a significant
under-the-hood changes.

`GenericTag` is the generalization of all tags as DST that owns all
memory of the tag. This tag can be created from bytes, thus, we can
ensure a lifetime and a valid memory range. This tag then can be
casted to specific implementations of `TagTrait`. We now never have to
increase or decrease the size of the referenced memory during a Tag
cast, as the GenericTag already holds all bytes that the more specific
type needs.

Assertions and the memory checks along the way ensure that nothing can
co wrong.

Further, the creation of various test data structures was modified to
fulfill the new guarantees, that are given in real-world scenarios and
are also what the compiler expects.
Philipp Schuster 8 月之前
父節點
當前提交
5def169369

+ 3 - 1
multiboot2/Cargo.toml

@@ -45,12 +45,14 @@ bitflags.workspace = true
 derive_more.workspace = true
 log.workspace = true
 
+ptr_meta = { version = "~0.2", default-features = false }
 # We only use a very basic type definition from this crate. To prevent MSRV
 # bumps from uefi-raw, I restrict this here. Upstream users are likely to have
 # two versions of this library in it, which is no problem, as we only use the
 # type definition.
 uefi-raw = { version = "~0.5", default-features = false }
-ptr_meta = { version = "~0.2", default-features = false }
+
+[dev-dependencies]
 
 [package.metadata.docs.rs]
 all-features = true

+ 2 - 0
multiboot2/Changelog.md

@@ -5,6 +5,8 @@
 - **Breaking** All functions that returns something useful are now `#[must_use]`
 - **Breaking** More public fields in tags were replaced by public getters, such
   as `SmbiosTag::major()`
+- **BREAKING:** `multiboot2::{StringError};` -> \
+  `multiboot2::util::{StringError};`
 - updated dependencies
 - MSRV is 1.75
 - documentation enhancements

+ 8 - 8
multiboot2/src/boot_information.rs

@@ -277,7 +277,7 @@ impl<'a> BootInformation<'a> {
     pub fn elf_sections(&self) -> Option<ElfSectionIter> {
         let tag = self.get_tag::<ElfSectionsTag>();
         tag.map(|t| {
-            assert!((t.entry_size * t.shndx) <= t.size);
+            assert!((t.entry_size * t.shndx) <= t.size() as u32);
             t.sections()
         })
     }
@@ -359,7 +359,7 @@ impl<'a> BootInformation<'a> {
     /// special handling is required. This is reflected by code-comments.
     ///
     /// ```no_run
-    /// use multiboot2::{BootInformation, BootInformationHeader, StringError, Tag, TagTrait, TagType, TagTypeId};
+    /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagTrait, TagType, TagTypeId};
     ///
     /// #[repr(C)]
     /// #[derive(multiboot2::Pointee)] // Only needed for DSTs.
@@ -374,17 +374,17 @@ impl<'a> BootInformation<'a> {
     /// impl TagTrait for CustomTag {
     ///     const ID: TagType = TagType::Custom(0x1337);
     ///
-    ///     fn dst_size(base_tag: &Tag) -> usize {
+    ///     fn dst_len(header: &TagHeader) -> usize {
     ///         // The size of the sized portion of the custom tag.
     ///         let tag_base_size = 8; // id + size is 8 byte in size
-    ///         assert!(base_tag.size >= 8);
-    ///         base_tag.size as usize - tag_base_size
+    ///         assert!(header.size >= 8);
+    ///         header.size as usize - tag_base_size
     ///     }
     /// }
     ///
     /// impl CustomTag {
     ///     fn name(&self) -> Result<&str, StringError> {
-    ///         Tag::parse_slice_as_string(&self.name)
+    ///         parse_slice_as_string(&self.name)
     ///     }
     /// }
     /// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader;
@@ -398,8 +398,8 @@ impl<'a> BootInformation<'a> {
     #[must_use]
     pub fn get_tag<TagT: TagTrait + ?Sized + 'a>(&'a self) -> Option<&'a TagT> {
         self.tags()
-            .find(|tag| tag.typ == TagT::ID)
-            .map(|tag| tag.cast_tag::<TagT>())
+            .find(|tag| tag.header().typ == TagT::ID)
+            .map(|tag| tag.cast::<TagT>())
     }
 
     /// Returns an iterator over all tags.

+ 29 - 34
multiboot2/src/boot_loader_name.rs

@@ -1,13 +1,13 @@
 //! Module for [`BootLoaderNameTag`].
 
-use crate::tag::{StringError, TagHeader};
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::tag::TagHeader;
+use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
 #[cfg(feature = "builder")]
 use {crate::builder::BoxedDst, alloc::vec::Vec};
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + mem::size_of::<u32>();
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>();
 
 /// The bootloader name tag.
 #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -61,7 +61,7 @@ impl BootLoaderNameTag {
     /// }
     /// ```
     pub fn name(&self) -> Result<&str, StringError> {
-        Tag::parse_slice_as_string(&self.name)
+        parse_slice_as_string(&self.name)
     }
 }
 
@@ -78,54 +78,49 @@ impl Debug for BootLoaderNameTag {
 impl TagTrait for BootLoaderNameTag {
     const ID: TagType = TagType::BootLoaderName;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::{BootLoaderNameTag, Tag, TagTrait, TagType};
-
-    const MSG: &str = "hello";
-
-    /// Returns the tag structure in bytes in little endian format.
-    fn get_bytes() -> std::vec::Vec<u8> {
-        // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string
-        let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32;
-        [
-            &((TagType::BootLoaderName.val()).to_le_bytes()),
-            &size.to_le_bytes(),
-            MSG.as_bytes(),
-            // Null Byte
-            &[0],
-        ]
-        .iter()
-        .flat_map(|bytes| bytes.iter())
-        .copied()
-        .collect()
+    use super::*;
+    use crate::tag::{GenericTag, TagBytesRef};
+    use crate::test_util::AlignedBytes;
+
+    #[rustfmt::skip]
+    fn get_bytes() -> AlignedBytes<16> {
+        AlignedBytes::new([
+            TagType::BootLoaderName.val() as u8, 0, 0, 0,
+            15, 0, 0, 0,
+            b'h', b'e', b'l', b'l', b'o', b'\0',
+            /* padding */
+            0, 0
+        ])
     }
 
     /// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn test_parse_str() {
-        let tag = get_bytes();
-        let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
-        let tag = tag.cast_tag::<BootLoaderNameTag>();
+        let bytes = get_bytes();
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let tag = tag.cast::<BootLoaderNameTag>();
         assert_eq!(tag.header.typ, TagType::BootLoaderName);
-        assert_eq!(tag.name().expect("must be valid UTF-8"), MSG);
+        assert_eq!(tag.name(), Ok("hello"));
     }
 
     /// Test to generate a tag from a given string.
     #[test]
     #[cfg(feature = "builder")]
+    #[ignore]
     fn test_build_str() {
-        let tag = BootLoaderNameTag::new(MSG);
+        let tag = BootLoaderNameTag::new("hello");
         let bytes = tag.as_bytes();
-        assert_eq!(bytes, get_bytes());
-        assert_eq!(tag.name(), Ok(MSG));
+        assert_eq!(bytes, &get_bytes()[..]);
+        assert_eq!(tag.name(), Ok("hello"));
 
         // test also some bigger message
         let tag = BootLoaderNameTag::new("AbCdEfGhUjK YEAH");

+ 20 - 25
multiboot2/src/builder/boxed_dst.rs

@@ -1,6 +1,7 @@
 //! Module for [`BoxedDst`].
 
-use crate::{Tag, TagTrait, TagTypeId};
+use crate::util::increase_to_alignment;
+use crate::{TagHeader, TagTrait, TagTypeId};
 use alloc::alloc::alloc;
 use core::alloc::Layout;
 use core::marker::PhantomData;
@@ -40,14 +41,10 @@ impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
 
         let tag_size = size_of::<TagTypeId>() + size_of::<u32>() + content.len();
 
-        // By using miri, I could figure out that there often are problems where
-        // miri thinks an allocation is smaller then necessary. Most probably
-        // due to not packed structs. Using packed structs however
-        // (especially with DSTs), is a crazy ass pain and unusable :/ Therefore,
-        // the best solution I can think of is to allocate a few byte more than
-        // necessary. I think that during runtime, everything works fine and
-        // that no memory issues are present.
-        let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary
+        // The size of [the allocation for] a value is always a multiple of its
+        // alignment.
+        // https://doc.rust-lang.org/reference/type-layout.html
+        let alloc_size = increase_to_alignment(tag_size);
         let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap();
         let ptr = unsafe { alloc(layout) };
         assert!(!ptr.is_null());
@@ -70,8 +67,8 @@ impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
             }
         }
 
-        let base_tag = unsafe { &*ptr.cast::<Tag>() };
-        let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag));
+        let base_tag = unsafe { &*ptr.cast::<TagHeader>() };
+        let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_len(base_tag));
 
         Self {
             ptr: NonNull::new(raw).unwrap(),
@@ -101,10 +98,12 @@ impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
 }
 
 #[cfg(test)]
+#[cfg(not(miri))]
 mod tests {
     use super::*;
-    use crate::tag::StringError;
-    use crate::TagType;
+    use crate::test_util::AlignedBytes;
+    use crate::{parse_slice_as_string, StringError, TagHeader, TagType};
+    use core::borrow::Borrow;
 
     const METADATA_SIZE: usize = 8;
 
@@ -118,25 +117,24 @@ mod tests {
 
     impl CustomTag {
         fn string(&self) -> Result<&str, StringError> {
-            Tag::parse_slice_as_string(&self.string)
+            parse_slice_as_string(&self.string)
         }
     }
 
     impl TagTrait for CustomTag {
         const ID: TagType = TagType::Custom(0x1337);
 
-        fn dst_size(base_tag: &Tag) -> usize {
-            assert!(base_tag.size as usize >= METADATA_SIZE);
-            base_tag.size as usize - METADATA_SIZE
+        fn dst_len(header: &TagHeader) -> usize {
+            assert!(header.size as usize >= METADATA_SIZE);
+            header.size as usize - METADATA_SIZE
         }
     }
 
     #[test]
     fn test_boxed_dst_tag() {
-        let content = b"hallo\0";
+        let content = AlignedBytes::new(*b"hallo\0");
         let content_rust_str = "hallo";
-
-        let tag = BoxedDst::<CustomTag>::new(content);
+        let tag = BoxedDst::<CustomTag>::new(content.borrow());
         assert_eq!(tag.typ, CustomTag::ID);
         assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
         assert_eq!(tag.string(), Ok(content_rust_str));
@@ -145,12 +143,9 @@ mod tests {
     #[test]
     fn can_hold_tag_trait() {
         const fn consume<T: TagTrait + ?Sized>(_: &T) {}
-        let content = b"hallo\0";
-
-        let tag = BoxedDst::<CustomTag>::new(content);
+        let content = AlignedBytes::new(*b"hallo\0");
+        let tag = BoxedDst::<CustomTag>::new(content.borrow());
         consume(tag.deref());
         consume(&*tag);
-        // Compiler not smart enough?
-        // consume(&tag);
     }
 }

+ 5 - 19
multiboot2/src/builder/information.rs

@@ -1,5 +1,6 @@
 //! Exports item [`InformationBuilder`].
 use crate::builder::{AsBytes, BoxedDst};
+use crate::util::increase_to_alignment;
 use crate::{
     BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag,
     EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag,
@@ -78,12 +79,6 @@ impl InformationBuilder {
         Self(Vec::new())
     }
 
-    /// Returns the provided number or the next multiple of 8. This is helpful
-    /// to ensure that the following tag starts at a 8-byte aligned boundary.
-    const fn size_or_up_aligned(size: usize) -> usize {
-        (size + 7) & !7
-    }
-
     /// Returns the expected length of the boot information, when the
     /// [`Self::build`]-method is called. This function assumes that the begin
     /// of the boot information is 8-byte aligned and automatically adds padding
@@ -92,10 +87,8 @@ impl InformationBuilder {
     pub fn expected_len(&self) -> usize {
         let tag_size_iter = self.0.iter().map(|(_, bytes)| bytes.len());
 
-        let payload_tags_size = tag_size_iter.fold(0, |acc, tag_size| {
-            // size_or_up_aligned: make sure next tag is 8-byte aligned
-            acc + Self::size_or_up_aligned(tag_size)
-        });
+        let payload_tags_size =
+            tag_size_iter.fold(0, |acc, tag_size| acc + increase_to_alignment(tag_size));
 
         size_of::<BootInformationHeader>() + payload_tags_size + size_of::<EndTag>()
     }
@@ -112,7 +105,7 @@ impl InformationBuilder {
 
         if tag_type != TagType::End {
             let size = tag_serialized.len();
-            let size_to_8_align = Self::size_or_up_aligned(size);
+            let size_to_8_align = increase_to_alignment(size);
             let size_to_8_align_diff = size_to_8_align - size;
             // fill zeroes so that next data block is 8-byte aligned
             dest_buf.extend([0].repeat(size_to_8_align_diff));
@@ -316,6 +309,7 @@ impl InformationBuilder {
 }
 
 #[cfg(test)]
+#[cfg(not(miri))]
 mod tests {
     use crate::builder::information::InformationBuilder;
     use crate::{BasicMemoryInfoTag, BootInformation, CommandLineTag, ModuleTag};
@@ -349,14 +343,6 @@ mod tests {
         builder
     }
 
-    #[test]
-    fn test_size_or_up_aligned() {
-        assert_eq!(0, InformationBuilder::size_or_up_aligned(0));
-        assert_eq!(8, InformationBuilder::size_or_up_aligned(1));
-        assert_eq!(8, InformationBuilder::size_or_up_aligned(8));
-        assert_eq!(16, InformationBuilder::size_or_up_aligned(9));
-    }
-
     /// Test of the `build` method in isolation specifically for miri to check
     /// for memory issues.
     #[test]

+ 27 - 32
multiboot2/src/command_line.rs

@@ -1,14 +1,14 @@
 //! Module for [`CommandLineTag`].
 
-use crate::tag::{StringError, TagHeader};
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::tag::TagHeader;
+use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
 use core::str;
 #[cfg(feature = "builder")]
 use {crate::builder::BoxedDst, alloc::vec::Vec};
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + mem::size_of::<u32>();
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>();
 
 /// This tag contains the command line string.
 ///
@@ -55,7 +55,7 @@ impl CommandLineTag {
     /// }
     /// ```
     pub fn cmdline(&self) -> Result<&str, StringError> {
-        Tag::parse_slice_as_string(&self.cmdline)
+        parse_slice_as_string(&self.cmdline)
     }
 }
 
@@ -72,54 +72,49 @@ impl Debug for CommandLineTag {
 impl TagTrait for CommandLineTag {
     const ID: TagType = TagType::Cmdline;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::tag::{GenericTag, TagBytesRef};
+    use crate::test_util::AlignedBytes;
 
-    const MSG: &str = "hello";
-
-    /// Returns the tag structure in bytes in little endian format.
-    fn get_bytes() -> std::vec::Vec<u8> {
-        // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string
-        let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32;
-        [
-            &((TagType::Cmdline.val()).to_le_bytes()),
-            &size.to_le_bytes(),
-            MSG.as_bytes(),
-            // Null Byte
-            &[0],
-        ]
-        .iter()
-        .flat_map(|bytes| bytes.iter())
-        .copied()
-        .collect()
+    #[rustfmt::skip]
+    fn get_bytes() -> AlignedBytes<16> {
+        AlignedBytes::new([
+            TagType::Cmdline.val() as u8, 0, 0, 0,
+            14, 0, 0, 0,
+            b'h', b'e', b'l', b'l', b'o',  b'\0',
+            /* padding */
+            0, 0
+        ])
     }
 
     /// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn test_parse_str() {
-        let tag = get_bytes();
-        let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
-        let tag = tag.cast_tag::<CommandLineTag>();
+        let bytes = get_bytes();
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let tag = tag.cast::<CommandLineTag>();
         assert_eq!(tag.header.typ, TagType::Cmdline);
-        assert_eq!(tag.cmdline().expect("must be valid UTF-8"), MSG);
+        assert_eq!(tag.cmdline(), Ok("hello"));
     }
 
     /// Test to generate a tag from a given string.
     #[test]
     #[cfg(feature = "builder")]
+    #[ignore]
     fn test_build_str() {
-        let tag = CommandLineTag::new(MSG);
+        let tag = CommandLineTag::new("hello");
         let bytes = tag.as_bytes();
-        assert_eq!(bytes, get_bytes());
-        assert_eq!(tag.cmdline(), Ok(MSG));
+        assert_eq!(bytes, &get_bytes()[..]);
+        assert_eq!(tag.cmdline(), Ok("hello"));
 
         // test also some bigger message
         let tag = CommandLineTag::new("AbCdEfGhUjK YEAH");

+ 6 - 6
multiboot2/src/efi.rs

@@ -7,7 +7,7 @@
 //! - [`EFIBootServicesNotExitedTag`]
 
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType};
+use crate::{TagTrait, TagType};
 use core::mem::size_of;
 
 /// EFI system table in 32 bit mode tag.
@@ -38,7 +38,7 @@ impl EFISdt32Tag {
 impl TagTrait for EFISdt32Tag {
     const ID: TagType = TagType::Efi32;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// EFI system table in 64 bit mode tag.
@@ -69,7 +69,7 @@ impl EFISdt64Tag {
 impl TagTrait for EFISdt64Tag {
     const ID: TagType = TagType::Efi64;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// Tag that contains the pointer to the boot loader's UEFI image handle
@@ -102,7 +102,7 @@ impl EFIImageHandle32Tag {
 impl TagTrait for EFIImageHandle32Tag {
     const ID: TagType = TagType::Efi32Ih;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// Tag that contains the pointer to the boot loader's UEFI image handle
@@ -135,7 +135,7 @@ impl EFIImageHandle64Tag {
 impl TagTrait for EFIImageHandle64Tag {
     const ID: TagType = TagType::Efi64Ih;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// EFI ExitBootServices was not called tag.
@@ -166,7 +166,7 @@ impl Default for EFIBootServicesNotExitedTag {
 impl TagTrait for EFIBootServicesNotExitedTag {
     const ID: TagType = TagType::EfiBs;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 #[cfg(all(test, feature = "builder"))]

+ 12 - 12
multiboot2/src/elf_sections.rs

@@ -2,12 +2,12 @@
 
 #[cfg(feature = "builder")]
 use crate::builder::BoxedDst;
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagHeader, TagTrait, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
 use core::str::Utf8Error;
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 4 * mem::size_of::<u32>();
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 3 * mem::size_of::<u32>();
 
 /// This tag contains the section header table from an ELF binary.
 // The sections iterator is provided via the [`ElfSectionsTag::sections`]
@@ -15,8 +15,7 @@ const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 4 * mem::size_of::<u3
 #[derive(ptr_meta::Pointee, PartialEq, Eq)]
 #[repr(C)]
 pub struct ElfSectionsTag {
-    typ: TagTypeId,
-    pub(crate) size: u32,
+    header: TagHeader,
     number_of_sections: u32,
     pub(crate) entry_size: u32,
     pub(crate) shndx: u32, // string table
@@ -64,20 +63,20 @@ impl ElfSectionsTag {
 impl TagTrait for ElfSectionsTag {
     const ID: TagType = TagType::ElfSections;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 
 impl Debug for ElfSectionsTag {
     fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
         f.debug_struct("ElfSectionsTag")
-            .field("typ", &{ self.typ })
-            .field("size", &{ self.size })
-            .field("number_of_sections", &{ self.number_of_sections })
-            .field("entry_size", &{ self.entry_size })
-            .field("shndx", &{ self.shndx })
+            .field("typ", &self.header.typ)
+            .field("size", &self.header.size)
+            .field("number_of_sections", &self.number_of_sections)
+            .field("entry_size", &self.entry_size)
+            .field("shndx", &self.shndx)
             .field("sections", &self.sections())
             .finish()
     }
@@ -85,6 +84,7 @@ impl Debug for ElfSectionsTag {
 
 /// An iterator over some ELF sections.
 #[derive(Clone)]
+/// TODO make this memory safe with lifetime capture.
 pub struct ElfSectionIter {
     current_section: *const u8,
     remaining_sections: u32,

+ 2 - 2
multiboot2/src/end.rs

@@ -1,6 +1,6 @@
 //! Module for [`EndTag`].
 
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagHeader, TagTrait, TagType, TagTypeId};
 
 /// The end tag ends the information struct.
 #[repr(C)]
@@ -22,7 +22,7 @@ impl Default for EndTag {
 impl TagTrait for EndTag {
     const ID: TagType = TagType::End;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 #[cfg(test)]

+ 4 - 4
multiboot2/src/framebuffer.rs

@@ -1,7 +1,7 @@
 //! Module for [`FramebufferTag`].
 
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagTrait, TagType, TagTypeId};
 use core::fmt::Debug;
 use core::mem;
 use core::slice;
@@ -183,9 +183,9 @@ impl FramebufferTag {
 impl TagTrait for FramebufferTag {
     const ID: TagType = TagType::Framebuffer;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 

+ 2 - 2
multiboot2/src/image_load_addr.rs

@@ -1,7 +1,7 @@
 //! Module for [`ImageLoadPhysAddrTag`].
 
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType};
+use crate::{TagTrait, TagType};
 #[cfg(feature = "builder")]
 use core::mem::size_of;
 
@@ -36,7 +36,7 @@ impl ImageLoadPhysAddrTag {
 impl TagTrait for ImageLoadPhysAddrTag {
     const ID: TagType = TagType::LoadBaseAddr;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 #[cfg(all(test, feature = "builder"))]

+ 32 - 63
multiboot2/src/lib.rs

@@ -12,7 +12,7 @@
 // now allow a few rules which are denied by the above statement
 // --> They are either ridiculous, not necessary, or we can't fix them.
 #![allow(clippy::multiple_crate_versions)]
-#![deny(missing_docs)]
+// #![deny(missing_docs)]
 #![deny(missing_debug_implementations)]
 #![deny(rustdoc::all)]
 // --- END STYLE CHECKS ---
@@ -56,6 +56,8 @@ extern crate bitflags;
 
 #[cfg(feature = "builder")]
 pub mod builder;
+#[cfg(test)]
+pub(crate) mod test_util;
 
 mod boot_information;
 mod boot_loader_name;
@@ -72,6 +74,7 @@ mod smbios;
 mod tag;
 mod tag_trait;
 mod tag_type;
+pub(crate) mod util;
 mod vbe_info;
 
 pub use boot_information::{BootInformation, BootInformationHeader, MbiLoadError};
@@ -94,9 +97,10 @@ pub use module::{ModuleIter, ModuleTag};
 pub use ptr_meta::Pointee;
 pub use rsdp::{RsdpV1Tag, RsdpV2Tag};
 pub use smbios::SmbiosTag;
-pub use tag::{StringError, Tag};
+pub use tag::TagHeader;
 pub use tag_trait::TagTrait;
 pub use tag_type::{TagType, TagTypeId};
+pub use util::{parse_slice_as_string, StringError};
 pub use vbe_info::{
     VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag,
     VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes,
@@ -107,11 +111,13 @@ pub use vbe_info::{
 /// machine state.
 pub const MAGIC: u32 = 0x36d76289;
 
+/// The required alignment for tags and the boot information.
+pub const ALIGNMENT: usize = 8;
+
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::memory_map::MemoryAreaType;
-    use crate::tag::StringError;
+    use crate::test_util::AlignedBytes;
 
     /// Compile time test to check if the boot information is Send and Sync.
     /// This test is relevant to give library users flexebility in passing the
@@ -119,10 +125,7 @@ mod tests {
     #[test]
     fn boot_information_is_send_and_sync() {
         fn accept<T: Send + Sync>(_: T) {}
-
-        #[repr(C, align(8))]
-        struct Bytes([u8; 16]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             16, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             0, 0, 0, 0, // end tag type
@@ -138,9 +141,7 @@ mod tests {
 
     #[test]
     fn no_tags() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 16]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             16, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             0, 0, 0, 0, // end tag type
@@ -163,9 +164,7 @@ mod tests {
     #[test]
     #[should_panic]
     fn invalid_total_size() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 15]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             15, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             0, 0, 0, 0, // end tag type
@@ -188,9 +187,7 @@ mod tests {
     #[test]
     #[should_panic]
     fn invalid_end_tag() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 16]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             16, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             0, 0, 0, 0, // end tag type
@@ -211,11 +208,8 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn name_tag() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 32]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             32, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             2, 0, 0, 0, // boot loader name tag type
@@ -246,14 +240,11 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn framebuffer_tag_rgb() {
         // direct RGB mode test:
         // taken from GRUB2 running in QEMU at
         // 1280x720 with 32bpp in BGRA format.
-        #[repr(C, align(8))]
-        struct Bytes([u8; 56]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             56, 0, 0, 0, // total size
             0, 0, 0, 0, // reserved
             8, 0, 0, 0, // framebuffer tag type
@@ -307,14 +298,11 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn framebuffer_tag_indexed() {
         // indexed mode test:
         // this is synthetic, as I can't get QEMU
         // to run in indexed color mode.
-        #[repr(C, align(8))]
-        struct Bytes([u8; 64]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             64, 0, 0, 0, // total size
             0, 0, 0, 0, // reserved
             8, 0, 0, 0, // framebuffer tag type
@@ -379,13 +367,10 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     #[allow(clippy::cognitive_complexity)]
     fn vbe_info_tag() {
         //Taken from GRUB2 running in QEMU.
-        #[repr(C, align(8))]
-        struct Bytes([u8; 800]);
-        let bytes = Bytes([
+        let bytes = AlignedBytes([
             32, 3, 0, 0, // Total size.
             0, 0, 0, 0, // Reserved
             7, 0, 0, 0, // Tag type.
@@ -551,11 +536,10 @@ mod tests {
     /// Tests to parse a MBI that was statically extracted from a test run with
     /// GRUB as bootloader.
     #[test]
+    // TODO fix Miri
     #[cfg_attr(miri, ignore)]
     fn grub2() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 960]);
-        let mut bytes: Bytes = Bytes([
+        let mut bytes = AlignedBytes([
             192, 3, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             1, 0, 0, 0, // boot command tag type
@@ -959,15 +943,14 @@ mod tests {
     }
 
     #[test]
+    // TODO fix Miri
     #[cfg_attr(miri, ignore)]
     fn elf_sections() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 168]);
-        let mut bytes: Bytes = Bytes([
+        let mut bytes = AlignedBytes([
             168, 0, 0, 0, // total_size
             0, 0, 0, 0, // reserved
             9, 0, 0, 0, // elf symbols tag type
-            20, 2, 0, 0, // elf symbols tag size
+            148, 0, 0, 0, // elf symbols tag size
             2, 0, 0, 0, // elf symbols num
             64, 0, 0, 0, // elf symbols entsize
             1, 0, 0, 0, // elf symbols shndx
@@ -1036,12 +1019,9 @@ mod tests {
     }
 
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn efi_memory_map() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 80]);
         // test that the EFI memory map is detected.
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             80, 0, 0, 0, // size
             0, 0, 0, 0, // reserved
             17, 0, 0, 0, // EFI memory map type
@@ -1117,7 +1097,6 @@ mod tests {
 
     /// Example for a custom tag.
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn get_custom_tag_from_mbi() {
         #[repr(C, align(8))]
         struct CustomTag {
@@ -1129,13 +1108,10 @@ mod tests {
         impl TagTrait for CustomTag {
             const ID: TagType = TagType::Custom(0x1337);
 
-            fn dst_size(_base_tag: &Tag) {}
+            fn dst_len(_tag_header: &TagHeader) {}
         }
-
-        #[repr(C, align(8))]
-        struct AlignedBytes([u8; 32]);
         // Raw bytes of a MBI that only contains the custom tag.
-        let bytes: AlignedBytes = AlignedBytes([
+        let bytes = AlignedBytes([
             32,
             0,
             0,
@@ -1183,7 +1159,6 @@ mod tests {
 
     /// Example for a custom DST tag.
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn get_custom_dst_tag_from_mbi() {
         #[repr(C)]
         #[derive(crate::Pointee)]
@@ -1195,25 +1170,22 @@ mod tests {
 
         impl CustomTag {
             fn name(&self) -> Result<&str, StringError> {
-                Tag::parse_slice_as_string(&self.name)
+                parse_slice_as_string(&self.name)
             }
         }
 
         impl TagTrait for CustomTag {
             const ID: TagType = TagType::Custom(0x1337);
 
-            fn dst_size(base_tag: &Tag) -> usize {
+            fn dst_len(header: &TagHeader) -> usize {
                 // The size of the sized portion of the command line tag.
                 let tag_base_size = 8;
-                assert!(base_tag.size >= 8);
-                base_tag.size as usize - tag_base_size
+                assert!(header.size >= 8);
+                header.size as usize - tag_base_size
             }
         }
-
-        #[repr(C, align(8))]
-        struct AlignedBytes([u8; 32]);
         // Raw bytes of a MBI that only contains the custom tag.
-        let bytes: AlignedBytes = AlignedBytes([
+        let bytes = AlignedBytes([
             32,
             0,
             0,
@@ -1261,11 +1233,8 @@ mod tests {
 
     /// Tests that `get_tag` can consume multiple types that implement `Into<TagTypeId>`
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn get_tag_into_variants() {
-        #[repr(C, align(8))]
-        struct Bytes([u8; 32]);
-        let bytes: Bytes = Bytes([
+        let bytes = AlignedBytes([
             32,
             0,
             0,

+ 9 - 9
multiboot2/src/memory_map.rs

@@ -6,14 +6,14 @@ pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc;
 pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType;
 
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagTrait, TagType, TagTypeId};
 use core::fmt::{Debug, Formatter};
 use core::marker::PhantomData;
 use core::mem;
 #[cfg(feature = "builder")]
 use {crate::builder::AsBytes, crate::builder::BoxedDst};
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 3 * mem::size_of::<u32>();
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
 
 /// This tag provides an initial host memory map (legacy boot, not UEFI).
 ///
@@ -75,9 +75,9 @@ impl MemoryMapTag {
 impl TagTrait for MemoryMapTag {
     const ID: TagType = TagType::Mmap;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        let size = base_tag.size as usize - METADATA_SIZE;
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        let size = header.size as usize - METADATA_SIZE;
         assert_eq!(size % mem::size_of::<MemoryArea>(), 0);
         size / mem::size_of::<MemoryArea>()
     }
@@ -287,7 +287,7 @@ impl BasicMemoryInfoTag {
 impl TagTrait for BasicMemoryInfoTag {
     const ID: TagType = TagType::BasicMeminfo;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 const EFI_METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 3 * mem::size_of::<u32>();
@@ -408,9 +408,9 @@ impl Debug for EFIMemoryMapTag {
 impl TagTrait for EFIMemoryMapTag {
     const ID: TagType = TagType::EfiMmap;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= EFI_METADATA_SIZE);
-        base_tag.size as usize - EFI_METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= EFI_METADATA_SIZE);
+        header.size as usize - EFI_METADATA_SIZE
     }
 }
 

+ 35 - 39
multiboot2/src/module.rs

@@ -1,13 +1,13 @@
 //! Module for [`ModuleTag`].
 
-use crate::tag::{StringError, TagHeader, TagIter};
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::tag::{TagHeader, TagIter};
+use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
 use core::fmt::{Debug, Formatter};
 use core::mem;
 #[cfg(feature = "builder")]
 use {crate::builder::BoxedDst, alloc::vec::Vec};
 
-const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + 3 * mem::size_of::<u32>();
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + 2 * mem::size_of::<u32>();
 
 /// The module tag can occur multiple times and specifies passed boot modules
 /// (blobs in memory). The tag itself doesn't include the blog, but references
@@ -51,7 +51,7 @@ impl ModuleTag {
     ///
     /// If the function returns `Err` then perhaps the memory is invalid.
     pub fn cmdline(&self) -> Result<&str, StringError> {
-        Tag::parse_slice_as_string(&self.cmdline)
+        parse_slice_as_string(&self.cmdline)
     }
 
     /// Start address of the module.
@@ -76,9 +76,9 @@ impl ModuleTag {
 impl TagTrait for ModuleTag {
     const ID: TagType = TagType::Module;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 
@@ -111,8 +111,8 @@ impl<'a> Iterator for ModuleIter<'a> {
 
     fn next(&mut self) -> Option<&'a ModuleTag> {
         self.iter
-            .find(|tag| tag.typ == TagType::Module)
-            .map(|tag| tag.cast_tag())
+            .find(|tag| tag.header().typ == TagType::Module)
+            .map(|tag| tag.cast())
     }
 }
 
@@ -128,49 +128,45 @@ impl<'a> Debug for ModuleIter<'a> {
 
 #[cfg(test)]
 mod tests {
-    use crate::{ModuleTag, Tag, TagTrait, TagType};
-
-    const MSG: &str = "hello";
-
-    /// Returns the tag structure in bytes in little endian format.
-    fn get_bytes() -> std::vec::Vec<u8> {
-        // size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string
-        //          4 bytes mod_start + 4 bytes mod_end
-        let size = (4 + 4 + 4 + 4 + MSG.as_bytes().len() + 1) as u32;
-        [
-            &((TagType::Module.val()).to_le_bytes()),
-            &size.to_le_bytes(),
-            &0_u32.to_le_bytes(),
-            &1_u32.to_le_bytes(),
-            MSG.as_bytes(),
-            // Null Byte
-            &[0],
-        ]
-        .iter()
-        .flat_map(|bytes| bytes.iter())
-        .copied()
-        .collect()
+    use super::*;
+    use crate::tag::{GenericTag, TagBytesRef};
+    use crate::test_util::AlignedBytes;
+
+    #[rustfmt::skip]
+    fn get_bytes() -> AlignedBytes<24> {
+        AlignedBytes::new([
+            TagType::Module.val() as u8, 0, 0, 0,
+            22, 0, 0, 0,
+            /* mod start */
+            0x00, 0xff, 0, 0,
+            /* mod end */
+            0xff, 0xff, 0, 0,
+            b'h', b'e', b'l', b'l', b'o', b'\0',
+            /* padding */
+            0, 0,
+        ])
     }
 
     /// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn test_parse_str() {
-        let tag = get_bytes();
-        let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
-        let tag = tag.cast_tag::<ModuleTag>();
+        let bytes = get_bytes();
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let tag = tag.cast::<ModuleTag>();
         assert_eq!(tag.header.typ, TagType::Module);
-        assert_eq!(tag.cmdline().expect("must be valid UTF-8"), MSG);
+        assert_eq!(tag.cmdline(), Ok("hello"));
     }
 
     /// Test to generate a tag from a given string.
     #[test]
     #[cfg(feature = "builder")]
+    #[ignore]
     fn test_build_str() {
-        let tag = ModuleTag::new(0, 1, MSG);
+        let tag = ModuleTag::new(0xff00, 0xffff, "hello");
         let bytes = tag.as_bytes();
-        assert_eq!(bytes, get_bytes());
-        assert_eq!(tag.cmdline(), Ok(MSG));
+        assert_eq!(bytes, &get_bytes()[..]);
+        assert_eq!(tag.cmdline(), Ok("hello"));
 
         // test also some bigger message
         let tag = ModuleTag::new(0, 1, "AbCdEfGhUjK YEAH");

+ 3 - 3
multiboot2/src/rsdp.rs

@@ -13,7 +13,7 @@
 //!
 
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType};
+use crate::{TagTrait, TagType};
 #[cfg(feature = "builder")]
 use core::mem::size_of;
 use core::slice;
@@ -94,7 +94,7 @@ impl RsdpV1Tag {
 impl TagTrait for RsdpV1Tag {
     const ID: TagType = TagType::AcpiV1;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// This tag contains a copy of RSDP as defined per ACPI 2.0 or later specification.
@@ -191,5 +191,5 @@ impl RsdpV2Tag {
 impl TagTrait for RsdpV2Tag {
     const ID: TagType = TagType::AcpiV2;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }

+ 33 - 28
multiboot2/src/smbios.rs

@@ -3,12 +3,11 @@
 #[cfg(feature = "builder")]
 use crate::builder::BoxedDst;
 use crate::tag::TagHeader;
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagTrait, TagType};
 use core::fmt::Debug;
 use core::mem;
 
-const METADATA_SIZE: usize =
-    mem::size_of::<TagTypeId>() + mem::size_of::<u32>() + mem::size_of::<u8>() * 8;
+const METADATA_SIZE: usize = mem::size_of::<TagHeader>() + mem::size_of::<u8>() * 8;
 
 /// This tag contains a copy of SMBIOS tables as well as their version.
 #[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -53,9 +52,9 @@ impl SmbiosTag {
 impl TagTrait for SmbiosTag {
     const ID: TagType = TagType::Smbios;
 
-    fn dst_size(base_tag: &Tag) -> usize {
-        assert!(base_tag.size as usize >= METADATA_SIZE);
-        base_tag.size as usize - METADATA_SIZE
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= METADATA_SIZE);
+        header.size as usize - METADATA_SIZE
     }
 }
 
@@ -73,41 +72,47 @@ impl Debug for SmbiosTag {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::tag::{GenericTag, TagBytesRef};
+    use crate::test_util::AlignedBytes;
 
-    /// Returns the tag structure in bytes in little endian format.
-    fn get_bytes() -> std::vec::Vec<u8> {
-        let tables = [0xabu8; 24];
-        // size is: 4 bytes for tag + 4 bytes for size + 1 byte for major and minor
-        // + 6 bytes reserved + the actual tables
-        let size = (4 + 4 + 1 + 1 + 6 + tables.len()) as u32;
-        let typ: u32 = TagType::Smbios.into();
-        let mut bytes = [typ.to_le_bytes(), size.to_le_bytes()].concat();
-        bytes.push(3);
-        bytes.push(0);
-        bytes.extend([0; 6]);
-        bytes.extend(tables);
-        bytes
+    #[rustfmt::skip]
+    fn get_bytes() -> AlignedBytes<32> {
+        AlignedBytes::new([
+            TagType::Smbios.val() as u8, 0, 0, 0,
+            25, 0, 0, 0,
+            /* major */
+            7,
+            /* minor */
+            42,
+            /* reserved */
+            0, 0, 0, 0, 0, 0,
+            /* table data */
+            0, 1, 2, 3, 4, 5, 6, 7, 8,
+            /* padding */
+            0, 0, 0, 0, 0, 0, 0
+        ])
     }
 
     /// Test to parse a given tag.
     #[test]
-    #[cfg_attr(miri, ignore)]
     fn test_parse() {
-        let tag = get_bytes();
-        let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
-        let tag = tag.cast_tag::<SmbiosTag>();
+        let bytes = get_bytes();
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let tag = tag.cast::<SmbiosTag>();
         assert_eq!(tag.header.typ, TagType::Smbios);
-        assert_eq!(tag.major, 3);
-        assert_eq!(tag.minor, 0);
-        assert_eq!(tag.tables, [0xabu8; 24]);
+        assert_eq!(tag.major, 7);
+        assert_eq!(tag.minor, 42);
+        assert_eq!(&tag.tables, [0, 1, 2, 3, 4, 5, 6, 7, 8]);
     }
 
     /// Test to generate a tag.
     #[test]
     #[cfg(feature = "builder")]
+    #[ignore]
     fn test_build() {
-        let tag = SmbiosTag::new(3, 0, &[0xabu8; 24]);
+        let tag = SmbiosTag::new(7, 42, &[0, 1, 2, 3, 4, 5, 6, 7, 8]);
         let bytes = tag.as_bytes();
-        assert_eq!(bytes, get_bytes());
+        assert_eq!(bytes, &get_bytes()[..]);
     }
 }

+ 368 - 124
multiboot2/src/tag.rs

@@ -1,45 +1,28 @@
 //! Module for the base tag definitions and helper types.
 //!
-//! The relevant exports of this module is [`Tag`].
-
-use crate::{TagTrait, TagType, TagTypeId};
-use core::fmt;
-use core::fmt::{Debug, Display, Formatter};
-use core::marker::PhantomData;
-use core::str::Utf8Error;
-
-/// Error type describing failures when parsing the string from a tag.
-#[derive(Debug, PartialEq, Eq, Clone)]
-pub enum StringError {
-    /// There is no terminating NUL character, although the specification
-    /// requires one.
-    MissingNul(core::ffi::FromBytesUntilNulError),
-    /// The sequence until the first NUL character is not valid UTF-8.
-    Utf8(Utf8Error),
-}
+//! The relevant exports of this module are [`TagHeader`], [`GenericTag`], and
+//! [`TagIter`].
+//!
+//! The (internal) workflow to parse a tag from bytes is the following:
+//! - `&[u8]` --> [`TagBytesRef`]
+//! - [`TagBytesRef`] --> [`TagHeader`]
+//! - [`TagBytesRef`] + [`TagHeader`] --> [`GenericTag`]
+//! - [`GenericTag`] --> cast to desired tag
 
-impl Display for StringError {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        write!(f, "{:?}", self)
-    }
-}
-
-#[cfg(feature = "unstable")]
-impl core::error::Error for StringError {
-    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
-        match self {
-            Self::MissingNul(e) => Some(e),
-            Self::Utf8(e) => Some(e),
-        }
-    }
-}
+use crate::util::increase_to_alignment;
+use crate::{TagTrait, TagType, TagTypeId, ALIGNMENT};
+use core::fmt::{Debug, Formatter};
+use core::mem;
+use core::ops::Deref;
+use core::ptr;
 
 /// The common header that all tags have in common. This type is ABI compatible.
+/// It is the sized counterpart of [`GenericTag`].
 ///
 /// Not to be confused with Multiboot header tags, which are something
 /// different.
 #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
-#[repr(C)]
+#[repr(C, align(8))] // Alignment also propagates to all tag types using this.
 pub struct TagHeader {
     /// The ABI-compatible [`TagType`].
     pub typ: TagTypeId, /* u32 */
@@ -58,141 +41,402 @@ impl TagHeader {
     }
 }
 
-/// Common base structure for all tags that can be passed via the Multiboot2
-/// Information Structure (MBI) to a Multiboot2 payload/program/kernel.
+/// Wraps a byte slice representing a tag, but guarantees that the memory
+/// requirements are fulfilled.
 ///
-/// Can be transformed to any other tag (sized or unsized/DST) via
-/// [`Tag::cast_tag`].
+/// This is the only type that can be used to construct a [`GenericTag`].
 ///
-/// Do not confuse them with the Multiboot2 header tags. They are something
-/// different.
-#[derive(Clone, Copy)]
+/// The main reason for this dedicated type is to create fine-grained unit-tests
+/// for Miri.
+///
+/// # Memory Requirements (for Multiboot and Rust/Miri)
+/// - At least as big as a `size_of<TagHeader>()`
+/// - at least [`ALIGNMENT`]-aligned
+/// - Length is multiple of [`ALIGNMENT`]. In other words, there are enough
+///   padding bytes until so that pointer coming right after the last byte
+///   is [`ALIGNMENT`]-aligned
+#[derive(Clone, Debug, PartialEq, Eq)]
+#[repr(transparent)]
+pub(crate) struct TagBytesRef<'a>(&'a [u8]);
+
+impl<'a> TryFrom<&'a [u8]> for TagBytesRef<'a> {
+    type Error = MemoryError;
+
+    fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
+        if value.len() < mem::size_of::<TagHeader>() {
+            return Err(MemoryError::MinLengthNotSatisfied);
+        }
+        // Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
+        if value.as_ptr().align_offset(ALIGNMENT) != 0 {
+            return Err(MemoryError::WrongAlignment);
+        }
+        let padding_bytes = value.len() % ALIGNMENT;
+        if padding_bytes != 0 {
+            return Err(MemoryError::MissingPadding);
+        }
+        Ok(Self(value))
+    }
+}
+
+impl<'a> Deref for TagBytesRef<'a> {
+    type Target = &'a [u8];
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+/// Errors that occur when constructing a [`TagBytesRef`].
+#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
+pub enum MemoryError {
+    /// The memory must be at least [`ALIGNMENT`]-aligned.
+    WrongAlignment,
+    /// The memory must cover at least the length of a [`TagHeader`].
+    MinLengthNotSatisfied,
+    /// The buffer misses the terminating padding to the next alignment
+    /// boundary.
+    // This is mainly relevant to satisfy Miri. As the spec also mandates an
+    // alignment, we can rely on this property.
+    MissingPadding,
+}
+
+/// A generic tag serving as base to cast to specific tags. This is a DST
+/// version of [`TagHeader`] that solves various type and memory safety
+/// problems by having a type that owns the whole memory of a tag.
+#[derive(Eq, Ord, PartialEq, PartialOrd, ptr_meta::Pointee)]
 #[repr(C)]
-#[allow(missing_docs)] // type will be removed soon anyway in its form
-pub struct Tag {
-    pub typ: TagTypeId, // u32
-    pub size: u32,
-    // followed by additional, tag specific fields
+pub struct GenericTag {
+    header: TagHeader,
+    /// Payload of the tag that is reflected in the `size` attribute, thus, no
+    /// padding bytes!
+    payload: [u8],
 }
 
-impl Tag {
-    /// Returns the underlying type of the tag.
-    #[must_use]
-    pub fn typ(&self) -> TagType {
-        self.typ.into()
+impl GenericTag {
+    /// Base size of the DST struct without the dynamic part.
+    const BASE_SIZE: usize = mem::size_of::<TagHeader>();
+
+    /// Creates a reference to a [`GenericTag`] from the provided `bytes`
+    /// [`TagBytesRef`].
+    pub(crate) fn ref_from(bytes: TagBytesRef) -> &Self {
+        let header = bytes.as_ptr().cast::<TagHeader>();
+        let header = unsafe { &*header };
+        let dst_len = Self::dst_len(header);
+        assert_eq!(header.size as usize, Self::BASE_SIZE + dst_len);
+
+        let generic_tag: *const GenericTag =
+            ptr_meta::from_raw_parts(bytes.as_ptr().cast(), dst_len);
+        let generic_tag = unsafe { &*generic_tag };
+
+        generic_tag
     }
 
-    /// Casts the base tag to the specific tag type.
-    #[must_use]
-    pub fn cast_tag<'a, T: TagTrait + ?Sized + 'a>(&'a self) -> &'a T {
-        assert_eq!(self.typ, T::ID);
-        // Safety: At this point, we trust that "self.size" and the size hint
-        // for DST tags are sane.
-        unsafe { TagTrait::from_base_tag(self) }
+    pub fn header(&self) -> &TagHeader {
+        &self.header
     }
 
-    /// Parses the provided byte sequence as Multiboot string, which maps to a
-    /// [`str`].
-    pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
-        let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?;
+    pub fn payload(&self) -> &[u8] {
+        &self.payload
+    }
 
-        cstr.to_str().map_err(StringError::Utf8)
+    /// Casts the generic tag to a specific [`TagTrait`] implementation which
+    /// may be a ZST or DST typed tag.
+    pub fn cast<T: TagTrait + ?Sized>(&self) -> &T {
+        let base_ptr = ptr::addr_of!(*self);
+        let t_dst_size = T::dst_len(&self.header);
+        let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
+        let t_ref = unsafe { &*t_ptr };
+        assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
+        t_ref
     }
 }
 
-impl Debug for Tag {
-    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
-        let tag_type = TagType::from(self.typ);
-
-        let mut debug = f.debug_struct("Tag");
-        debug.field("typ", &tag_type);
-
-        if !matches!(tag_type, TagType::Custom(_)) {
-            debug.field("typ (numeric)", &(u32::from(self.typ)));
-        }
+impl Debug for GenericTag {
+    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
+        f.debug_struct("GenericTag")
+            .field("header", &self.header)
+            .field("payload", &"<bytes>")
+            .finish()
+    }
+}
 
-        debug.field("size", &(self.size));
+impl TagTrait for GenericTag {
+    const ID: TagType = TagType::Custom(0xffffffff);
 
-        debug.finish()
+    fn dst_len(header: &TagHeader) -> usize {
+        assert!(header.size as usize >= Self::BASE_SIZE);
+        header.size as usize - Self::BASE_SIZE
     }
 }
 
-/// Iterates the MBI's tags from the first tag to the end tag.
+/// Iterates the tags of the MBI from the first tag to the end tag. THe end tag
+/// included.
 #[derive(Clone, Debug)]
 pub struct TagIter<'a> {
-    /// Pointer to the next tag. Updated in each iteration.
-    pub current: *const Tag,
-    /// The pointer right after the MBI. Used for additional bounds checking.
-    end_ptr_exclusive: *const u8,
-    /// Lifetime capture of the MBI's memory.
-    _mem: PhantomData<&'a ()>,
+    /// Absolute offset to next tag and updated in each iteration.
+    next_tag_offset: usize,
+    exclusive_end: *const u8,
+    buffer: &'a [u8],
 }
 
 impl<'a> TagIter<'a> {
     /// Creates a new iterator
     pub fn new(mem: &'a [u8]) -> Self {
+        // Assert alignment.
         assert_eq!(mem.as_ptr().align_offset(8), 0);
+
+        let exclusive_end = unsafe { mem.as_ptr().add(mem.len()) };
+
         TagIter {
-            current: mem.as_ptr().cast(),
-            end_ptr_exclusive: unsafe { mem.as_ptr().add(mem.len()) },
-            _mem: PhantomData,
+            next_tag_offset: 0,
+            buffer: mem,
+            exclusive_end,
         }
     }
 }
 
 impl<'a> Iterator for TagIter<'a> {
-    type Item = &'a Tag;
+    type Item = &'a GenericTag;
 
-    fn next(&mut self) -> Option<&'a Tag> {
-        // This never failed so far. But better be safe.
-        assert!(self.current.cast::<u8>() < self.end_ptr_exclusive);
+    fn next(&mut self) -> Option<Self::Item> {
+        let next_ptr = unsafe { self.buffer.as_ptr().add(self.next_tag_offset) };
 
-        let tag = unsafe { &*self.current };
-        match tag.typ() {
-            TagType::End => None, // end tag
-            _ => {
-                // We return the tag and update self.current already to the next
-                // tag.
+        if next_ptr == self.exclusive_end {
+            return None;
+        }
+        assert!(next_ptr < self.exclusive_end);
 
-                // next pointer (rounded up to 8-byte alignment)
-                let ptr_offset = (tag.size as usize + 7) & !7;
+        let next_tag_ptr = next_ptr.cast::<TagHeader>();
 
-                // go to next tag
-                self.current = unsafe { self.current.cast::<u8>().add(ptr_offset).cast::<Tag>() };
+        let tag_hdr = unsafe { &*next_tag_ptr };
 
-                Some(tag)
-            }
-        }
+        // Get relevant byte portion for the next tag. This includes padding
+        // bytes to fulfill Rust memory guarantees. Otherwise, Miri complains.
+        // See <https://doc.rust-lang.org/reference/type-layout.html>.
+        let bytes = {
+            let from = self.next_tag_offset;
+            let to = from + tag_hdr.size as usize;
+            // The size of [the allocation for] a value is always a multiple of its
+            // alignment.
+            // https://doc.rust-lang.org/reference/type-layout.html
+            let to = increase_to_alignment(to);
+
+            // Update ptr for next iteration.
+            self.next_tag_offset += to - from;
+
+            &self.buffer[from..to]
+        };
+
+        // Should never fail at this point.
+        let tag_bytes = TagBytesRef::try_from(bytes).unwrap();
+
+        Some(GenericTag::ref_from(tag_bytes))
     }
 }
 
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::test_util::AlignedBytes;
+    use core::mem;
+
+    #[test]
+    fn test_new_generic_tag() {
+        let bytes = AlignedBytes::new([
+            /* id: 0xffff_ffff */
+            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
+            16, 0, 0, 0, /* field a: 0xdead_beef */
+            0xde, 0xad, 0xbe, 0xef, /* field b: 0x1337_1337 */
+            0x13, 0x37, 0x13, 0x37,
+        ]);
+
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        assert_eq!(tag.header.typ, 0xffff_ffff);
+        assert_eq!(tag.header.size, 16);
+        assert_eq!(tag.payload.len(), 8);
+    }
+
+    #[test]
+    fn test_cast_generic_tag_to_sized_tag() {
+        #[repr(C)]
+        struct CustomTag {
+            tag_header: TagHeader,
+            a: u32,
+            b: u32,
+        }
+
+        impl TagTrait for CustomTag {
+            const ID: TagType = TagType::End;
+
+            fn dst_len(_header: &TagHeader) -> Self::Metadata {}
+        }
+
+        let bytes = AlignedBytes([
+            /* id: 0xffff_ffff */
+            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
+            16, 0, 0, 0, /* field a: 0xdead_beef */
+            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
+            0x37, 0x13, 0x37, 0x13,
+        ]);
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let custom_tag = tag.cast::<CustomTag>();
+
+        assert_eq!(mem::size_of_val(custom_tag), 16);
+        assert_eq!(custom_tag.a, 0xdead_beef);
+        assert_eq!(custom_tag.b, 0x1337_1337);
+    }
+
+    #[test]
+    fn test_cast_generic_tag_to_dynamically_sized_tag() {
+        #[repr(C)]
+        #[derive(ptr_meta::Pointee)]
+        struct CustomDstTag {
+            tag_header: TagHeader,
+            a: u32,
+            payload: [u8],
+        }
+
+        impl TagTrait for CustomDstTag {
+            const ID: TagType = TagType::End;
+
+            fn dst_len(header: &TagHeader) -> Self::Metadata {
+                let base_size = mem::size_of::<TagHeader>() + mem::size_of::<u32>();
+                header.size as usize - base_size
+            }
+        }
+
+        let bytes = AlignedBytes([
+            /* id: 0xffff_ffff */
+            0xff_u8, 0xff_u8, 0xff_u8, 0xff_u8, /* id: 16 */
+            16, 0, 0, 0, /* field a: 0xdead_beef */
+            0xef, 0xbe, 0xad, 0xde, /* field b: 0x1337_1337 */
+            0x37, 0x13, 0x37, 0x13,
+        ]);
+
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        let custom_tag = tag.cast::<CustomDstTag>();
+
+        assert_eq!(mem::size_of_val(custom_tag), 16);
+        assert_eq!(custom_tag.a, 0xdead_beef);
+        assert_eq!(custom_tag.payload.len(), 4);
+        assert_eq!(custom_tag.payload[0], 0x37);
+        assert_eq!(custom_tag.payload[1], 0x13);
+        assert_eq!(custom_tag.payload[2], 0x37);
+        assert_eq!(custom_tag.payload[3], 0x13);
+    }
+
+    #[test]
+    fn test_tag_bytes_ref() {
+        let empty: &[u8] = &[];
+        assert_eq!(
+            TagBytesRef::try_from(empty),
+            Err(MemoryError::MinLengthNotSatisfied)
+        );
+
+        let slice = &[0_u8, 1, 2, 3, 4, 5, 6];
+        assert_eq!(
+            TagBytesRef::try_from(&slice[..]),
+            Err(MemoryError::MinLengthNotSatisfied)
+        );
+
+        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]);
+        // Guaranteed wrong alignment
+        let unaligned_slice = &slice[3..];
+        assert_eq!(
+            TagBytesRef::try_from(&unaligned_slice[..]),
+            Err(MemoryError::WrongAlignment)
+        );
+
+        let slice = AlignedBytes([0_u8, 1, 2, 3, 4, 5, 6, 7]);
+        let slice = &slice[..];
+        assert_eq!(TagBytesRef::try_from(slice), Ok(TagBytesRef(slice)));
+    }
 
     #[test]
-    fn parse_slice_as_string() {
-        // empty slice is invalid
-        assert!(matches!(
-            Tag::parse_slice_as_string(&[]),
-            Err(StringError::MissingNul(_))
-        ));
-        // empty string is fine
-        assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok(""));
-        // reject invalid utf8
-        assert!(matches!(
-            Tag::parse_slice_as_string(&[0xff, 0x00]),
-            Err(StringError::Utf8(_))
-        ));
-        // reject missing null
-        assert!(matches!(
-            Tag::parse_slice_as_string(b"hello"),
-            Err(StringError::MissingNul(_))
-        ));
-        // must not include final null
-        assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello"));
-        assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello"));
-        // must skip everytihng after first null
-        assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello"));
+    fn test_create_generic_tag() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes::new(
+            [
+                TagType::Cmdline.val() as u8, 0, 0, 0,
+                /* Tag size */
+                18, 0, 0, 0,
+                /* Some payload.  */
+                0, 1, 2, 3,
+                4, 5, 6, 7,
+                8, 9,
+                // Padding
+                0, 0, 0, 0, 0, 0
+            ],
+        );
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+        assert_eq!(tag.header.typ, TagType::Cmdline);
+        assert_eq!(tag.header.size, 8 + 10);
+    }
+
+    #[test]
+    fn test_cast_generic_tag_to_generic_tag() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes::new(
+            [
+                TagType::Cmdline.val() as u8, 0, 0, 0,
+                /* Tag size */
+                18, 0, 0, 0,
+                /* Some payload.  */
+                0, 1, 2, 3,
+                4, 5, 6, 7,
+                8, 9,
+                // Padding
+                0, 0, 0, 0, 0, 0
+            ],
+        );
+        let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
+        let tag = GenericTag::ref_from(bytes);
+
+        // Main objective here is also that this test passes Miri.
+        let tag = tag.cast::<GenericTag>();
+        assert_eq!(tag.header.typ, TagType::Cmdline);
+        assert_eq!(tag.header.size, 8 + 10);
+    }
+
+    #[test]
+    fn test_tag_iter() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes::new(
+            [
+                /* Some minimal tag.  */
+                0xff, 0, 0, 0,
+                8, 0, 0, 0,
+                /* Some tag with payload.  */
+                0xfe, 0, 0, 0,
+                12, 0, 0, 0,
+                1, 2, 3, 4,
+                // Padding
+                0, 0, 0, 0,
+                /* End tag */
+                0, 0, 0, 0,
+                8, 0, 0, 0,
+            ],
+        );
+        let mut iter = TagIter::new(&bytes[..]);
+        let first = iter.next().unwrap();
+        assert_eq!(first.header.typ, TagType::Custom(0xff));
+        assert_eq!(first.header.size, 8);
+        assert!(first.payload.is_empty());
+
+        let second = iter.next().unwrap();
+        assert_eq!(second.header.typ, TagType::Custom(0xfe));
+        assert_eq!(second.header.size, 12);
+        assert_eq!(&second.payload, &[1, 2, 3, 4]);
+
+        let third = iter.next().unwrap();
+        assert_eq!(third.header.typ, TagType::End);
+        assert_eq!(third.header.size, 8);
+        assert!(first.payload.is_empty());
+
+        assert_eq!(iter.next(), None);
     }
 }

+ 6 - 19
multiboot2/src/tag_trait.rs

@@ -1,10 +1,11 @@
 //! Module for [`TagTrait`].
 
-use crate::{Tag, TagType};
+use crate::tag::TagHeader;
+use crate::TagType;
 use ptr_meta::Pointee;
 
 /// A trait to abstract over all sized and unsized tags (DSTs). For sized tags,
-/// this trait does not much. For DSTs, a [`TagTrait::dst_size`] implementation
+/// this trait does not much. For DSTs, a [`TagTrait::dst_len`] implementation
 /// must be provided, which returns the right size hint for the dynamically
 /// sized portion of the struct.
 ///
@@ -22,12 +23,12 @@ pub trait TagTrait: Pointee {
     ///
     /// For sized tags, this just returns `()`. For DSTs, this returns an
     /// `usize`.
-    fn dst_size(base_tag: &Tag) -> Self::Metadata;
+    fn dst_len(header: &TagHeader) -> Self::Metadata;
 
     /// Returns the tag as the common base tag structure.
-    fn as_base_tag(&self) -> &Tag {
+    fn as_base_tag(&self) -> &TagHeader {
         let ptr = core::ptr::addr_of!(*self);
-        unsafe { &*ptr.cast::<Tag>() }
+        unsafe { &*ptr.cast::<TagHeader>() }
     }
 
     /// Returns the total size of the tag. The depends on the `size` field of
@@ -43,18 +44,4 @@ pub trait TagTrait: Pointee {
         let ptr = core::ptr::addr_of!(*self);
         unsafe { core::slice::from_raw_parts(ptr.cast(), self.size()) }
     }
-
-    /// Creates a reference to a (dynamically sized) tag type in a safe way.
-    /// DST tags need to implement a proper [`Self::dst_size`] implementation.
-    ///
-    /// # Safety
-    /// Callers must be sure that the "size" field of the provided [`Tag`] is
-    /// sane and the underlying memory valid. The implementation of this trait
-    /// **must have** a correct [`Self::dst_size`] implementation.
-    #[must_use]
-    unsafe fn from_base_tag(tag: &Tag) -> &Self {
-        let ptr = core::ptr::addr_of!(*tag);
-        let ptr = ptr_meta::from_raw_parts(ptr.cast(), Self::dst_size(tag));
-        &*ptr
-    }
 }

+ 87 - 0
multiboot2/src/test_util.rs

@@ -0,0 +1,87 @@
+//! Various test utilities.
+
+use crate::ALIGNMENT;
+use core::borrow::Borrow;
+use core::ops::Deref;
+
+/// Helper to 8-byte align the underlying bytes, as mandated in the Multiboot2
+/// spec. With this type, one can create manual and raw Multiboot2 boot
+/// information or just the bytes for simple tags, in a manual and raw approach.
+#[cfg(test)]
+#[repr(C, align(8))]
+pub(crate) struct AlignedBytes<const N: usize>(pub [u8; N]);
+
+impl<const N: usize> AlignedBytes<N> {
+    pub const fn new(bytes: [u8; N]) -> Self {
+        Self(bytes)
+    }
+}
+
+impl<const N: usize> Borrow<[u8; N]> for AlignedBytes<N> {
+    fn borrow(&self) -> &[u8; N] {
+        &self.0
+    }
+}
+
+impl<const N: usize> Borrow<[u8]> for AlignedBytes<N> {
+    fn borrow(&self) -> &[u8] {
+        &self.0
+    }
+}
+
+impl<const N: usize> Deref for AlignedBytes<N> {
+    type Target = [u8; N];
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+// The tests down below are all Miri-approved.
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::tag::TagBytesRef;
+    use core::mem;
+    use core::ptr::addr_of;
+
+    #[test]
+    fn abi() {
+        let bytes = AlignedBytes([0]);
+        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
+        assert_eq!(bytes.as_ptr().align_offset(8), 0);
+        assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
+
+        let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7]);
+        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
+        assert_eq!(bytes.as_ptr().align_offset(8), 0);
+        assert_eq!((addr_of!(bytes[0])).align_offset(8), 0);
+
+        let bytes = AlignedBytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
+        assert_eq!(mem::align_of_val(&bytes), ALIGNMENT);
+        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);
+    }
+
+    #[test]
+    fn compatible_with_tag_bytes_ref_type() {
+        #[rustfmt::skip]
+        let bytes = AlignedBytes(
+            [
+                /* tag id */
+                0, 0, 0, 0,
+                /* size */
+                14, 0, 0, 0,
+                /* arbitrary payload */
+                1, 2, 3, 4,
+                5, 6,
+                /* padding */
+                0, 0,
+            ]
+        );
+        let _a = TagBytesRef::try_from(&bytes[..]).unwrap();
+    }
+}

+ 90 - 0
multiboot2/src/util.rs

@@ -0,0 +1,90 @@
+//! Various utilities.
+
+use crate::ALIGNMENT;
+use core::fmt;
+use core::fmt::{Display, Formatter};
+use core::str::Utf8Error;
+
+/// Error type describing failures when parsing the string from a tag.
+#[derive(Debug, PartialEq, Eq, Clone)]
+pub enum StringError {
+    /// There is no terminating NUL character, although the specification
+    /// requires one.
+    MissingNul(core::ffi::FromBytesUntilNulError),
+    /// The sequence until the first NUL character is not valid UTF-8.
+    Utf8(Utf8Error),
+}
+
+impl Display for StringError {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        write!(f, "{:?}", self)
+    }
+}
+
+#[cfg(feature = "unstable")]
+impl core::error::Error for StringError {
+    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
+        match self {
+            Self::MissingNul(e) => Some(e),
+            Self::Utf8(e) => Some(e),
+        }
+    }
+}
+
+/// Parses the provided byte sequence as Multiboot string, which maps to a
+/// [`str`].
+pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
+    let cstr = core::ffi::CStr::from_bytes_until_nul(bytes).map_err(StringError::MissingNul)?;
+    cstr.to_str().map_err(StringError::Utf8)
+}
+
+/// Increases the given size to the next alignment boundary, if it is not a
+/// multiple of the alignment yet. This is relevant as in Rust's [type layout],
+/// the allocated size of a type is always a multiple of the alignment, even
+/// if the type is smaller.
+///
+/// [type layout]: https://doc.rust-lang.org/reference/type-layout.html
+pub const fn increase_to_alignment(size: usize) -> usize {
+    let mask = ALIGNMENT - 1;
+    (size + mask) & !mask
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_slice_as_string() {
+        // empty slice is invalid
+        assert!(matches!(
+            parse_slice_as_string(&[]),
+            Err(StringError::MissingNul(_))
+        ));
+        // empty string is fine
+        assert_eq!(parse_slice_as_string(&[0x00]), Ok(""));
+        // reject invalid utf8
+        assert!(matches!(
+            parse_slice_as_string(&[0xff, 0x00]),
+            Err(StringError::Utf8(_))
+        ));
+        // reject missing null
+        assert!(matches!(
+            parse_slice_as_string(b"hello"),
+            Err(StringError::MissingNul(_))
+        ));
+        // must not include final null
+        assert_eq!(parse_slice_as_string(b"hello\0"), Ok("hello"));
+        assert_eq!(parse_slice_as_string(b"hello\0\0"), Ok("hello"));
+        // must skip everytihng after first null
+        assert_eq!(parse_slice_as_string(b"hello\0foo"), Ok("hello"));
+    }
+
+    #[test]
+    fn test_increase_to_alignment() {
+        assert_eq!(increase_to_alignment(0), 0);
+        assert_eq!(increase_to_alignment(1), 8);
+        assert_eq!(increase_to_alignment(7), 8);
+        assert_eq!(increase_to_alignment(8), 8);
+        assert_eq!(increase_to_alignment(9), 16);
+    }
+}

+ 2 - 2
multiboot2/src/vbe_info.rs

@@ -1,6 +1,6 @@
 //! Module for [`VBEInfoTag`].
 
-use crate::{Tag, TagTrait, TagType, TagTypeId};
+use crate::{TagHeader, TagTrait, TagType, TagTypeId};
 use core::fmt;
 
 /// This tag contains VBE metadata, VBE controller information returned by the
@@ -42,7 +42,7 @@ pub struct VBEInfoTag {
 impl TagTrait for VBEInfoTag {
     const ID: TagType = TagType::Vbe;
 
-    fn dst_size(_base_tag: &Tag) {}
+    fn dst_len(_: &TagHeader) {}
 }
 
 /// VBE controller information.