From 1130f9488208482693c3474add467b67482af58d Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:54:50 +0200 Subject: [PATCH 01/16] multiboot2-common: add raw_type! macro for ABI-safe raw newtypes Several #[repr(C)] structures across the workspace store fields typed as Rust enums with a restricted set of valid discriminants. As these structures are parsed from raw bootloader-provided memory, any unknown value is undefined behavior. The macro generates the fix once: a #[repr(transparent)] newtype (Raw suffix) for which every bit pattern is valid, a high-level open-set enum with a generated Custom fallback variant, and all conversions between newtype, enum, and integer. --- multiboot2-common/CHANGELOG.md | 4 + multiboot2-common/src/lib.rs | 1 + multiboot2-common/src/raw.rs | 308 +++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+) create mode 100644 multiboot2-common/src/raw.rs diff --git a/multiboot2-common/CHANGELOG.md b/multiboot2-common/CHANGELOG.md index f627a209..17130847 100644 --- a/multiboot2-common/CHANGELOG.md +++ b/multiboot2-common/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- Added the `raw_type!` macro that generates an ABI-safe `#[repr(transparent)]` + newtype plus a corresponding high-level open-set enum, including all + conversions between them and the underlying integer. + ## v0.5.0 (2026-08-24) - Fixed undefined behavior in `DynSizedStructure::cast`: the target size is now diff --git a/multiboot2-common/src/lib.rs b/multiboot2-common/src/lib.rs index 0c039964..d3c076f6 100644 --- a/multiboot2-common/src/lib.rs +++ b/multiboot2-common/src/lib.rs @@ -255,6 +255,7 @@ pub mod test_utils; mod boxed; mod bytes_ref; mod iter; +mod raw; mod tag; #[cfg(feature = "alloc")] diff --git a/multiboot2-common/src/raw.rs b/multiboot2-common/src/raw.rs new file mode 100644 index 00000000..874b2df7 --- /dev/null +++ b/multiboot2-common/src/raw.rs @@ -0,0 +1,308 @@ +//! Module for the `raw_type` macro. + +/// Defines a pair of an ABI-compatible raw newtype and a corresponding +/// convenient, high-level open-set enum, along with all conversions between +/// the newtype, the enum, and the underlying integer. +/// +/// The newtype behaves like the plain integer (`Copy`, comparisons, and +/// conversions) but carries the semantics of the enum: [`Debug`] prints the +/// variant name together with the raw value (e.g. `Foo(0)`) and [`Display`] +/// prints just the variant name (e.g. `Foo`); values without a specified +/// semantic print as `Custom(x)`. It is safe to use in `#[repr(C)]` +/// structures parsed from raw memory, as every bit pattern is valid for it. The enum assigns each specified value to a +/// variant; all other values are mapped to the automatically added `Custom` +/// variant, which carries the raw integer. By convention, the newtype +/// carries the name of the enum plus a `Raw` suffix. +/// +/// [`Debug`]: core::fmt::Debug +/// [`Display`]: core::fmt::Display +/// +/// # Example +/// +/// ``` +/// multiboot2_common::raw_type! { +/// /// Binary representation of a demo type. +/// pub struct DemoTypeRaw(u32); +/// +/// /// High-level abstraction of the possible demo types. +/// pub enum DemoType { +/// /// The first defined type. +/// Foo = 0, +/// /// The second defined type. +/// Bar = 1, +/// } +/// } +/// +/// let raw = DemoTypeRaw::new(1); +/// assert_eq!(raw, DemoType::Bar); +/// assert_eq!(DemoType::from(DemoTypeRaw::new(42)), DemoType::Custom(42)); +/// ``` +#[macro_export] +macro_rules! raw_type { + ( + $(#[$raw_attr:meta])* + $raw_vis:vis struct $Raw:ident($int:ty); + + $(#[$enum_attr:meta])* + $enum_vis:vis enum $Enum:ident { + $( + $(#[$variant_attr:meta])* + $Variant:ident = $value:literal, + )+ + } + ) => { + $(#[$raw_attr])* + #[repr(transparent)] + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + $raw_vis struct $Raw($int); + + impl $Raw { + /// Constructs a new instance from the raw binary value. + #[must_use] + pub const fn new(val: $int) -> Self { + Self(val) + } + + /// Returns the raw binary value. + #[must_use] + pub const fn get(self) -> $int { + self.0 + } + } + + impl ::core::fmt::Debug for $Raw { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Debug::fmt(&$Enum::from_val(self.0), f) + } + } + + impl ::core::fmt::Display for $Raw { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + ::core::fmt::Display::fmt(&$Enum::from_val(self.0), f) + } + } + + $(#[$enum_attr])* + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] + $enum_vis enum $Enum { + $( + $(#[$variant_attr])* + $Variant, + )+ + /// Value without a specified semantic in the specification. + Custom($int), + } + + impl $Enum { + /// Returns the raw binary value. + #[must_use] + pub const fn val(self) -> $int { + match self { + $(Self::$Variant => $value,)+ + Self::Custom(val) => val, + } + } + + /// Constructs the variant corresponding to the raw binary value. + /// + /// Values without a specified semantic are mapped to + /// [`Self::Custom`]. + #[must_use] + pub const fn from_val(val: $int) -> Self { + match val { + $($value => Self::$Variant,)+ + val => Self::Custom(val), + } + } + } + + impl ::core::fmt::Debug for $Enum { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + $(Self::$Variant => f.debug_tuple(stringify!($Variant)).field(&$value).finish(),)+ + Self::Custom(val) => f.debug_tuple("Custom").field(val).finish(), + } + } + } + + impl ::core::fmt::Display for $Enum { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + $(Self::$Variant => f.write_str(stringify!($Variant)),)+ + Self::Custom(val) => write!(f, "Custom({val})"), + } + } + } + + impl ::core::convert::From<$int> for $Raw { + fn from(val: $int) -> Self { + Self::new(val) + } + } + + impl ::core::convert::From<$Raw> for $int { + fn from(raw: $Raw) -> Self { + raw.get() + } + } + + impl ::core::convert::From<$int> for $Enum { + fn from(val: $int) -> Self { + Self::from_val(val) + } + } + + impl ::core::convert::From<$Enum> for $int { + fn from(val: $Enum) -> Self { + val.val() + } + } + + impl ::core::convert::From<$Raw> for $Enum { + fn from(raw: $Raw) -> Self { + Self::from_val(raw.get()) + } + } + + impl ::core::convert::From<$Enum> for $Raw { + fn from(val: $Enum) -> Self { + Self::new(val.val()) + } + } + + impl ::core::cmp::PartialEq<$Enum> for $Raw { + fn eq(&self, other: &$Enum) -> bool { + self.0 == other.val() + } + } + + impl ::core::cmp::PartialEq<$Raw> for $Enum { + fn eq(&self, other: &$Raw) -> bool { + self.val() == other.0 + } + } + + impl ::core::cmp::PartialEq<$int> for $Raw { + fn eq(&self, other: &$int) -> bool { + self.0 == *other + } + } + + impl ::core::cmp::PartialEq<$Raw> for $int { + fn eq(&self, other: &$Raw) -> bool { + *self == other.0 + } + } + + impl ::core::cmp::PartialEq<$int> for $Enum { + fn eq(&self, other: &$int) -> bool { + self.val() == *other + } + } + + impl ::core::cmp::PartialEq<$Enum> for $int { + fn eq(&self, other: &$Enum) -> bool { + *self == other.val() + } + } + }; +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeSet, HashSet}; + + crate::raw_type! { + /// Binary representation of a test type. + pub struct TestRaw(u16); + + /// High-level abstraction of a test type. + pub enum TestType { + /// The first defined value. + Foo = 0, + /// A defined value with a gap to the previous one. + Bar = 42, + } + } + + /// The newtype must be binary compatible with the underlying integer. + #[test] + fn test_layout() { + assert_eq!(size_of::(), size_of::()); + assert_eq!(align_of::(), align_of::()); + } + + #[test] + fn test_const_constructors_and_getters() { + const RAW: TestRaw = TestRaw::new(42); + const VAL: u16 = RAW.get(); + const TYP: TestType = TestType::from_val(VAL); + assert_eq!(VAL, 42); + assert_eq!(TYP, TestType::Bar); + assert_eq!(TYP.val(), 42); + } + + /// Every raw value must be constructible and must round-trip through + /// the newtype and the enum, including values unknown to the + /// specification. + #[test] + fn test_roundtrip() { + for val in [0_u16, 42, 1337, u16::MAX] { + let raw = TestRaw::from(val); + let typ = TestType::from(raw); + assert_eq!(u16::from(raw), val); + assert_eq!(u16::from(typ), val); + assert_eq!(TestRaw::from(typ), raw); + } + } + + #[test] + fn test_from_val() { + assert_eq!(TestType::from_val(0), TestType::Foo); + assert_eq!(TestType::from_val(42), TestType::Bar); + assert_eq!(TestType::from_val(7), TestType::Custom(7)); + assert_eq!(TestType::Foo.val(), 0); + assert_eq!(TestType::Bar.val(), 42); + assert_eq!(TestType::Custom(7).val(), 7); + } + + /// All three representations must be comparable with each other. + #[test] + fn test_partial_eq() { + assert_eq!(TestRaw::new(42), TestType::Bar); + assert_eq!(TestType::Bar, TestRaw::new(42)); + assert_eq!(TestRaw::new(42), 42); + assert_eq!(42, TestRaw::new(42)); + assert_eq!(TestType::Bar, 42); + assert_eq!(42, TestType::Bar); + assert_eq!(TestRaw::new(7), TestType::Custom(7)); + assert_ne!(TestRaw::new(0), TestType::Bar); + } + + /// The types must debug-print the semantic of their value together + /// with the raw value. + #[test] + fn test_debug() { + assert_eq!(format!("{:?}", TestRaw::new(0)), "Foo(0)"); + assert_eq!(format!("{:?}", TestRaw::new(7)), "Custom(7)"); + assert_eq!(format!("{:?}", TestType::Bar), "Bar(42)"); + } + + /// The types must display-print just the semantic of their value. + #[test] + fn test_display() { + assert_eq!(format!("{}", TestRaw::new(0)), "Foo"); + assert_eq!(format!("{}", TestRaw::new(7)), "Custom(7)"); + assert_eq!(format!("{}", TestType::Bar), "Bar"); + } + + /// Both types must be usable in ordered and hashed collections. + #[test] + fn test_ord_and_hash() { + let set = BTreeSet::from([TestType::Bar, TestType::Foo, TestType::Bar]); + assert!(set.iter().zip(set.iter().skip(1)).all(|(a, b)| a < b)); + + let set = HashSet::from([TestRaw::new(0), TestRaw::new(1)]); + assert_eq!(set.len(), 2); + } +} From fe1f22b762f9cc9c8737c456e787a7a8344da82f Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:55:32 +0200 Subject: [PATCH 02/16] multiboot2: port TagTypeId to raw_type! as TagTypeRaw Renames the hand-written newtype to follow the new Raw-suffix convention of the macro. No behavioral change. The MbiTagTypeId re-export of multiboot2-header follows the rename to MbiTagTypeRaw so that every commit builds. --- multiboot2-header/src/information_request.rs | 12 +- multiboot2-header/src/lib.rs | 2 +- multiboot2/CHANGELOG.md | 4 + multiboot2/src/boot_information.rs | 2 +- multiboot2/src/lib.rs | 8 +- multiboot2/src/memory_map.rs | 4 +- multiboot2/src/network.rs | 4 +- multiboot2/src/tag.rs | 8 +- multiboot2/src/tag_type.rs | 469 +++++-------------- 9 files changed, 133 insertions(+), 380 deletions(-) diff --git a/multiboot2-header/src/information_request.rs b/multiboot2-header/src/information_request.rs index 3a1eee06..006b1cdc 100644 --- a/multiboot2-header/src/information_request.rs +++ b/multiboot2-header/src/information_request.rs @@ -1,5 +1,5 @@ use crate::{HeaderTagFlag, HeaderTagHeader}; -use crate::{HeaderTagType, MbiTagTypeId}; +use crate::{HeaderTagType, MbiTagTypeRaw}; use core::fmt; use core::fmt::{Debug, Formatter}; #[cfg(feature = "builder")] @@ -14,14 +14,14 @@ use {alloc::boxed::Box, core::slice}; #[repr(C, align(8))] pub struct InformationRequestHeaderTag { header: HeaderTagHeader, - requests: [MbiTagTypeId], + requests: [MbiTagTypeRaw], } impl InformationRequestHeaderTag { /// Creates a new object. #[cfg(feature = "builder")] #[must_use] - pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeId]) -> Box { + pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeRaw]) -> Box { let header = HeaderTagHeader::new(HeaderTagType::InformationRequest, flags, 0); // SAFETY: The memory we are using is valid. let requests = unsafe { @@ -51,7 +51,7 @@ impl InformationRequestHeaderTag { /// Returns the requests as array #[must_use] - pub const fn requests(&self) -> &[MbiTagTypeId] { + pub const fn requests(&self) -> &[MbiTagTypeRaw] { &self.requests } } @@ -74,8 +74,8 @@ impl MaybeDynSized for InformationRequestHeaderTag { fn dst_len(header: &Self::Header) -> Self::Metadata { let dst_size = header.size() as usize - Self::BASE_SIZE; - assert_eq!(dst_size % size_of::(), 0); - dst_size / size_of::() + assert_eq!(dst_size % size_of::(), 0); + dst_size / size_of::() } } diff --git a/multiboot2-header/src/lib.rs b/multiboot2-header/src/lib.rs index 2cb76cf3..3a8c4ab3 100644 --- a/multiboot2-header/src/lib.rs +++ b/multiboot2-header/src/lib.rs @@ -103,4 +103,4 @@ pub use self::uefi_bs::*; pub use builder::Builder; /// Re-export of [`multiboot2::TagType`] from `multiboot2`-crate. -pub use multiboot2::{TagType as MbiTagType, TagTypeId as MbiTagTypeId}; +pub use multiboot2::{TagType as MbiTagType, TagTypeRaw as MbiTagTypeRaw}; diff --git a/multiboot2/CHANGELOG.md b/multiboot2/CHANGELOG.md index d19e4a8a..4155d215 100644 --- a/multiboot2/CHANGELOG.md +++ b/multiboot2/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- **Breaking:** Renamed `TagTypeId` to `TagTypeRaw`; it is now generated by the + `raw_type!` macro from `multiboot2-common` and gained more conversions. + `TagHeader::new()` now takes `impl Into`. + ## v0.26.0 / v0.26.1 (2026-08-24) - Fixed `BootdevTag` and `ApmTag` reporting a tag size that included Rust struct diff --git a/multiboot2/src/boot_information.rs b/multiboot2/src/boot_information.rs index d01826d8..3d7ab125 100644 --- a/multiboot2/src/boot_information.rs +++ b/multiboot2/src/boot_information.rs @@ -372,7 +372,7 @@ impl<'a> BootInformation<'a> { /// /// ```no_run /// use std::mem; - /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagType, TagTypeId}; /// + /// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagType, TagTypeRaw}; /// /// use multiboot2_common::{MaybeDynSized, Tag}; /// /// #[repr(C)] diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index 545f50a2..a1c0a0f7 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -119,7 +119,7 @@ pub use ptr_meta::Pointee; pub use rsdp::{RsdpV1Tag, RsdpV2Tag}; pub use smbios::SmbiosTag; pub use tag::TagHeader; -pub use tag_type::{TagType, TagTypeId}; +pub use tag_type::{TagType, TagTypeRaw}; pub use util::{StringError, parse_slice_as_string}; pub use vbe_info::{ VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag, @@ -1233,7 +1233,7 @@ mod tests { fn get_repeated_custom_tags_from_mbi() { #[repr(C, align(8))] struct CustomTag { - tag: TagTypeId, + tag: TagTypeRaw, size: u32, foo: u32, } @@ -1329,7 +1329,7 @@ mod tests { #[repr(C)] #[derive(crate::Pointee)] struct CustomTag { - tag: TagTypeId, + tag: TagTypeRaw, size: u32, name: [u8], } @@ -1408,7 +1408,7 @@ mod tests { assert_eq!(tag.name(), Ok("hello")); } - /// Tests that `get_tag` can consume multiple types that implement `Into` + /// Tests that `get_tag` can consume multiple types that implement `Into` #[test] fn get_tag_into_variants() { let bytes = AlignedBytes([ diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index 4f2a385f..fbac68e6 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -6,7 +6,7 @@ pub use uefi_raw::table::boot::MemoryDescriptor as EFIMemoryDesc; pub use uefi_raw::table::boot::MemoryType as EFIMemoryAreaType; use crate::tag::TagHeader; -use crate::{TagType, TagTypeId}; +use crate::{TagType, TagTypeRaw}; use core::fmt::{Debug, Formatter}; use core::marker::PhantomData; use multiboot2_common::{MaybeDynSized, Tag}; @@ -407,7 +407,7 @@ impl Debug for EFIMemoryMapTag { impl MaybeDynSized for EFIMemoryMapTag { type Header = TagHeader; - const BASE_SIZE: usize = size_of::() + 3 * size_of::(); + const BASE_SIZE: usize = size_of::() + 3 * size_of::(); fn dst_len(header: &TagHeader) -> usize { assert!(header.size as usize >= Self::BASE_SIZE); diff --git a/multiboot2/src/network.rs b/multiboot2/src/network.rs index d7d77f9b..e264f292 100644 --- a/multiboot2/src/network.rs +++ b/multiboot2/src/network.rs @@ -1,6 +1,6 @@ //! Module for [`NetworkTag`]. -use crate::{TagHeader, TagType, TagTypeId}; +use crate::{TagHeader, TagType, TagTypeRaw}; use multiboot2_common::{MaybeDynSized, Tag}; use ptr_meta::Pointee; #[cfg(feature = "builder")] @@ -10,7 +10,7 @@ use {alloc::boxed::Box, multiboot2_common::new_boxed}; #[derive(Debug, Pointee)] #[repr(C, align(8))] pub struct NetworkTag { - typ: TagTypeId, + typ: TagTypeRaw, size: u32, dhcpack: [u8], } diff --git a/multiboot2/src/tag.rs b/multiboot2/src/tag.rs index 574be7b2..9f489718 100644 --- a/multiboot2/src/tag.rs +++ b/multiboot2/src/tag.rs @@ -1,6 +1,6 @@ //! Module for the base tag definition [`TagHeader`]. -use crate::TagTypeId; +use crate::{TagType, TagTypeRaw}; use core::fmt::Debug; use multiboot2_common::Header; @@ -16,7 +16,7 @@ pub struct TagHeader { /// The ABI-compatible [`TagType`]. /// /// [`TagType`]: crate::TagType - pub typ: TagTypeId, /* u32 */ + pub typ: TagTypeRaw, /* u32 */ /// The total size of the tag including the header. pub size: u32, // Followed by optional additional tag specific fields. @@ -24,9 +24,9 @@ pub struct TagHeader { impl TagHeader { /// Creates a new header. - pub fn new(typ: impl Into, size: u32) -> Self { + pub fn new(typ: impl Into, size: u32) -> Self { Self { - typ: typ.into(), + typ: TagTypeRaw::new(typ.into().val()), size, } } diff --git a/multiboot2/src/tag_type.rs b/multiboot2/src/tag_type.rs index e19b8d2c..f0edf853 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -1,368 +1,117 @@ //! Module for tag types. //! -//! The relevant exports of this module are [`TagTypeId`] and [`TagType`]. +//! The relevant exports of this module are [`TagTypeRaw`] and [`TagType`]. -use core::fmt::{Debug, Formatter}; -use core::hash::Hash; - -/// Serialized form of [`TagType`] that matches the binary representation -/// (`u32`). -/// -/// The abstraction corresponds to the `typ`/`type` field of a Multiboot2 -/// [`TagHeader`]. This type can easily be created from or converted to -/// [`TagType`]. -/// -/// [`TagHeader`]: crate::TagHeader -#[repr(transparent)] -#[derive(Copy, Clone, PartialOrd, PartialEq, Eq, Ord, Hash)] -pub struct TagTypeId(u32); - -impl TagTypeId { - /// Constructor. - #[must_use] - pub const fn new(val: u32) -> Self { - Self(val) - } -} - -impl Debug for TagTypeId { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let tag_type = TagType::from(*self); - Debug::fmt(&tag_type, f) - } -} - -/// Higher level abstraction for [`TagTypeId`] that assigns each possible value -/// to a specific semantic according to the specification. -/// -/// Additionally, it allows to use the [`TagType::Custom`] variant. It is -/// **not binary compatible** with [`TagTypeId`]. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum TagType { - /// Tag `0`: Marks the end of the tags. - End, - /// Tag `1`: Additional command line string. - /// For example `''` or `'--my-custom-option foo --provided by_grub`, if - /// your GRUB config contains `multiboot2 /boot/multiboot2-binary.elf --my-custom-option foo --provided by_grub` - Cmdline, - /// Tag `2`: Name of the bootloader, e.g. 'GRUB 2.04-1ubuntu44.2' - BootLoaderName, - /// Tag `3`: Additional Multiboot modules, which are BLOBs provided in - /// memory. For example an initial ram disk with essential drivers. - Module, - /// Tag `4`: `mem_lower` and `mem_upper` indicate the amount of lower and - /// upper memory, respectively, in kilobytes. Lower memory starts at - /// address 0, and upper memory starts at address 1 megabyte. The maximum - /// possible value for lower memory is 640 kilobytes. The value returned - /// for upper memory is maximally the address of the first upper memory - /// hole minus 1 megabyte. It is not guaranteed to be this value. +multiboot2_common::raw_type! { + /// Serialized form of [`TagType`] that matches the binary representation + /// (`u32`). /// - /// This tag may not be provided by some bootloaders on EFI platforms if - /// EFI boot services are enabled and available for the loaded image (EFI - /// boot services not terminated tag exists in Multiboot2 information - /// structure). - BasicMeminfo, - /// Tag `5`: This tag indicates which BIOS disk device the bootloader - /// loaded the OS image from. If the OS image was not loaded from a BIOS - /// disk, then this tag must not be present. The operating system may use - /// this field as a hint for determining its own root device, but is not - /// required to. - Bootdev, - /// Tag `6`: Memory map. The map provided is guaranteed to list all - /// standard RAM that should be available for normal use. This type however - /// includes the regions occupied by kernel, mbi, segments and modules. - /// Kernel must take care not to overwrite these regions. + /// The abstraction corresponds to the `typ`/`type` field of a Multiboot2 + /// [`TagHeader`]. This type can easily be created from or converted to + /// [`TagType`]. /// - /// This tag may not be provided by some bootloaders on EFI platforms if - /// EFI boot services are enabled and available for the loaded image (EFI - /// boot services not terminated tag exists in Multiboot2 information - /// structure). - Mmap, - /// Tag `7`: Contains the VBE control information returned by the VBE - /// Function `0x00` and VBE mode information returned by the VBE Function - /// `0x01`, respectively. Note that VBE 3.0 defines another protected mode - /// interface which is incompatible with the old one. If you want to use - /// the new protected mode interface, you will have to find the table - /// yourself. - Vbe, - /// Tag `8`: Framebuffer. - Framebuffer, - /// Tag `9`: This tag contains section header table from an ELF kernel, the - /// size of each entry, number of entries, and the string table used as the - /// index of names. They correspond to the `shdr_*` entries (`shdr_num`, - /// etc.) in the Executable and Linkable Format (ELF) specification in the - /// program header. - ElfSections, - /// Tag `10`: APM table. See Advanced Power Management (APM) BIOS Interface - /// Specification, for more information. - Apm, - /// Tag `11`: This tag contains pointer to i386 EFI system table. - Efi32, - /// Tag `12`: This tag contains pointer to amd64 EFI system table. - Efi64, - /// Tag `13`: This tag contains a copy of SMBIOS tables as well as their - /// version. - Smbios, - /// Tag `14`: Also called "AcpiOld" in other multiboot2 implementations. - AcpiV1, - /// Tag `15`: Refers to version 2 and later of Acpi. - /// Also called "AcpiNew" in other multiboot2 implementations. - AcpiV2, - /// Tag `16`: This tag contains network information in the format specified - /// as DHCP. It may be either a real DHCP reply or just the configuration - /// info in the same format. This tag appears once - /// per card. - Network, - /// Tag `17`: This tag contains EFI memory map as per EFI specification. - /// This tag may not be provided by some bootloaders on EFI platforms if - /// EFI boot services are enabled and available for the loaded image (EFI - /// boot services not terminated tag exists in Multiboot2 information - /// structure). - EfiMmap, - /// Tag `18`: This tag indicates ExitBootServices wasn't called. - EfiBs, - /// Tag `19`: This tag contains pointer to EFI i386 image handle. Usually - /// it is bootloader image handle. - Efi32Ih, - /// Tag `20`: This tag contains pointer to EFI amd64 image handle. Usually - /// it is bootloader image handle. - Efi64Ih, - /// Tag `21`: This tag contains image load base physical address. The spec - /// tells *"It is provided only if image has relocatable header tag."* but - /// experience showed that this is not true for at least GRUB 2. - LoadBaseAddr, - /// Custom tag types `> 21`. The Multiboot2 spec doesn't explicitly allow - /// or disallow them. Bootloader and OS developers are free to use custom - /// tags. - Custom(u32), -} - -impl TagType { - /// Convenient wrapper to get the underlying `u32` representation of the tag. - #[must_use] - pub fn val(&self) -> u32 { - u32::from(*self) - } -} - -/// Relevant `From` implementations for conversions between `u32`, -/// [`TagTypeId`], and [`TagType`]. -mod primitive_conversion_impls { - use super::*; - use core::mem::transmute; - - impl From for TagTypeId { - fn from(value: u32) -> Self { - // SAFETY: the type has repr(transparent) - unsafe { transmute(value) } - } - } - - impl From for u32 { - fn from(value: TagTypeId) -> Self { - value.0 as _ - } - } - - impl From for TagType { - fn from(value: u32) -> Self { - match value { - 0 => Self::End, - 1 => Self::Cmdline, - 2 => Self::BootLoaderName, - 3 => Self::Module, - 4 => Self::BasicMeminfo, - 5 => Self::Bootdev, - 6 => Self::Mmap, - 7 => Self::Vbe, - 8 => Self::Framebuffer, - 9 => Self::ElfSections, - 10 => Self::Apm, - 11 => Self::Efi32, - 12 => Self::Efi64, - 13 => Self::Smbios, - 14 => Self::AcpiV1, - 15 => Self::AcpiV2, - 16 => Self::Network, - 17 => Self::EfiMmap, - 18 => Self::EfiBs, - 19 => Self::Efi32Ih, - 20 => Self::Efi64Ih, - 21 => Self::LoadBaseAddr, - c => Self::Custom(c), - } - } - } - - impl From for u32 { - fn from(value: TagType) -> Self { - match value { - TagType::End => 0, - TagType::Cmdline => 1, - TagType::BootLoaderName => 2, - TagType::Module => 3, - TagType::BasicMeminfo => 4, - TagType::Bootdev => 5, - TagType::Mmap => 6, - TagType::Vbe => 7, - TagType::Framebuffer => 8, - TagType::ElfSections => 9, - TagType::Apm => 10, - TagType::Efi32 => 11, - TagType::Efi64 => 12, - TagType::Smbios => 13, - TagType::AcpiV1 => 14, - TagType::AcpiV2 => 15, - TagType::Network => 16, - TagType::EfiMmap => 17, - TagType::EfiBs => 18, - TagType::Efi32Ih => 19, - TagType::Efi64Ih => 20, - TagType::LoadBaseAddr => 21, - TagType::Custom(c) => c, - } - } - } -} - -/// `From` implementations for conversions between [`TagTypeId`] and -/// [`TagType`]. -mod intermediate_conversion_impls { - use super::*; - - impl From for TagType { - fn from(value: TagTypeId) -> Self { - let value = u32::from(value); - Self::from(value) - } - } - - impl From for TagTypeId { - fn from(value: TagType) -> Self { - let value = u32::from(value); - Self::from(value) - } - } -} - -/// Implements `PartialEq` between [`TagTypeId`] and [`TagType`]. Two values are -/// equal if their `u32` representation is equal. Additionally, `u32` can be -/// compared with [`TagTypeId`]. -mod partial_eq_impls { - use super::*; - - impl PartialEq for TagType { - fn eq(&self, other: &TagTypeId) -> bool { - let this = u32::from(*self); - let that = u32::from(*other); - this == that - } - } - - // each compare/equal direction must be implemented manually - impl PartialEq for TagTypeId { - fn eq(&self, other: &TagType) -> bool { - other.eq(self) - } - } - - impl PartialEq for TagTypeId { - fn eq(&self, other: &u32) -> bool { - let this = u32::from(*self); - this == *other - } - } - - impl PartialEq for u32 { - fn eq(&self, other: &TagTypeId) -> bool { - other.eq(self) - } - } + /// [`TagHeader`]: crate::TagHeader + pub struct TagTypeRaw(u32); - impl PartialEq for TagType { - fn eq(&self, other: &u32) -> bool { - let this = u32::from(*self); - this == *other - } - } - - impl PartialEq for u32 { - fn eq(&self, other: &TagType) -> bool { - other.eq(self) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_hashset() { - let mut set = std::collections::HashSet::new(); - set.insert(TagType::Cmdline); - set.insert(TagType::ElfSections); - set.insert(TagType::BootLoaderName); - set.insert(TagType::LoadBaseAddr); - set.insert(TagType::LoadBaseAddr); - assert_eq!(set.len(), 4); - println!("{set:#?}"); - } - - #[test] - fn test_btreeset() { - let mut set = std::collections::BTreeSet::new(); - set.insert(TagType::Cmdline); - set.insert(TagType::ElfSections); - set.insert(TagType::BootLoaderName); - set.insert(TagType::LoadBaseAddr); - set.insert(TagType::LoadBaseAddr); - assert_eq!(set.len(), 4); - for (current, next) in set.iter().zip(set.iter().skip(1)) { - assert!(current < next); - } - println!("{set:#?}"); - } - - /// Tests for equality when one type is u32 and the other the enum representation. - #[test] - fn test_partial_eq_u32() { - assert_eq!(21, TagType::LoadBaseAddr); - assert_eq!(TagType::LoadBaseAddr, 21); - assert_eq!(21, TagTypeId(21)); - assert_eq!(TagTypeId(21), 21); - assert_eq!(42, TagType::Custom(42)); - assert_eq!(TagType::Custom(42), 42); - } - - /// Tests the construction of [`TagTypeId`] from primitive `u32` values. - #[test] - #[expect(non_snake_case)] - fn test_TagTypeId() { - assert_eq!(size_of::(), size_of::()); - assert_eq!(align_of::(), align_of::()); - - for i in 0..50_u32 { - let val: TagTypeId = i.into(); - let val2: TagType = val.into(); - assert_eq!(val, val2); - } - - let tag_custom: u32 = 0x1337; - let tag_custom: TagTypeId = tag_custom.into(); - let tag_custom: TagType = tag_custom.into(); - matches!(tag_custom, TagType::Custom(0x1337)); - } - - /// Tests the construction of [`TagTypeId`] from primitive `u32` values for - /// specified and custom tags. - #[test] - fn test_from_and_to_tag_type_id() { - for i in 0..1_000 { - let tag_type_id = TagTypeId::new(i); - let tag_type_from_id = TagType::from(tag_type_id); - let tag_type_from_u16 = TagType::from(i); - assert_eq!(tag_type_from_id, tag_type_from_u16) - } + /// Higher level abstraction for [`TagTypeRaw`] that assigns each possible + /// value to a specific semantic according to the specification. + /// + /// Additionally, it allows to use the [`TagType::Custom`] variant for + /// custom tag types `> 21`. The Multiboot2 spec doesn't explicitly allow + /// or disallow them; bootloader and OS developers are free to use custom + /// tags. It is **not binary compatible** with [`TagTypeRaw`]. + pub enum TagType { + /// Tag `0`: Marks the end of the tags. + End = 0, + /// Tag `1`: Additional command line string. + /// For example `''` or `'--my-custom-option foo --provided by_grub`, if + /// your GRUB config contains `multiboot2 /boot/multiboot2-binary.elf --my-custom-option foo --provided by_grub` + Cmdline = 1, + /// Tag `2`: Name of the bootloader, e.g. 'GRUB 2.04-1ubuntu44.2' + BootLoaderName = 2, + /// Tag `3`: Additional Multiboot modules, which are BLOBs provided in + /// memory. For example an initial ram disk with essential drivers. + Module = 3, + /// Tag `4`: `mem_lower` and `mem_upper` indicate the amount of lower and + /// upper memory, respectively, in kilobytes. Lower memory starts at + /// address 0, and upper memory starts at address 1 megabyte. The maximum + /// possible value for lower memory is 640 kilobytes. The value returned + /// for upper memory is maximally the address of the first upper memory + /// hole minus 1 megabyte. It is not guaranteed to be this value. + /// + /// This tag may not be provided by some bootloaders on EFI platforms if + /// EFI boot services are enabled and available for the loaded image (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + BasicMeminfo = 4, + /// Tag `5`: This tag indicates which BIOS disk device the bootloader + /// loaded the OS image from. If the OS image was not loaded from a BIOS + /// disk, then this tag must not be present. The operating system may use + /// this field as a hint for determining its own root device, but is not + /// required to. + Bootdev = 5, + /// Tag `6`: Memory map. The map provided is guaranteed to list all + /// standard RAM that should be available for normal use. This type however + /// includes the regions occupied by kernel, mbi, segments and modules. + /// Kernel must take care not to overwrite these regions. + /// + /// This tag may not be provided by some bootloaders on EFI platforms if + /// EFI boot services are enabled and available for the loaded image (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + Mmap = 6, + /// Tag `7`: Contains the VBE control information returned by the VBE + /// Function `0x00` and VBE mode information returned by the VBE Function + /// `0x01`, respectively. Note that VBE 3.0 defines another protected mode + /// interface which is incompatible with the old one. If you want to use + /// the new protected mode interface, you will have to find the table + /// yourself. + Vbe = 7, + /// Tag `8`: Framebuffer. + Framebuffer = 8, + /// Tag `9`: This tag contains section header table from an ELF kernel, the + /// size of each entry, number of entries, and the string table used as the + /// index of names. They correspond to the `shdr_*` entries (`shdr_num`, + /// etc.) in the Executable and Linkable Format (ELF) specification in the + /// program header. + ElfSections = 9, + /// Tag `10`: APM table. See Advanced Power Management (APM) BIOS Interface + /// Specification, for more information. + Apm = 10, + /// Tag `11`: This tag contains pointer to i386 EFI system table. + Efi32 = 11, + /// Tag `12`: This tag contains pointer to amd64 EFI system table. + Efi64 = 12, + /// Tag `13`: This tag contains a copy of SMBIOS tables as well as their + /// version. + Smbios = 13, + /// Tag `14`: Also called "AcpiOld" in other multiboot2 implementations. + AcpiV1 = 14, + /// Tag `15`: Refers to version 2 and later of Acpi. + /// Also called "AcpiNew" in other multiboot2 implementations. + AcpiV2 = 15, + /// Tag `16`: This tag contains network information in the format specified + /// as DHCP. It may be either a real DHCP reply or just the configuration + /// info in the same format. This tag appears once + /// per card. + Network = 16, + /// Tag `17`: This tag contains EFI memory map as per EFI specification. + /// This tag may not be provided by some bootloaders on EFI platforms if + /// EFI boot services are enabled and available for the loaded image (EFI + /// boot services not terminated tag exists in Multiboot2 information + /// structure). + EfiMmap = 17, + /// Tag `18`: This tag indicates ExitBootServices wasn't called. + EfiBs = 18, + /// Tag `19`: This tag contains pointer to EFI i386 image handle. Usually + /// it is bootloader image handle. + Efi32Ih = 19, + /// Tag `20`: This tag contains pointer to EFI amd64 image handle. Usually + /// it is bootloader image handle. + Efi64Ih = 20, + /// Tag `21`: This tag contains image load base physical address. The spec + /// tells *"It is provided only if image has relocatable header tag."* but + /// experience showed that this is not true for at least GRUB 2. + LoadBaseAddr = 21, } } From 628213e55fd400440ca9acc18ea31ddca56eb015 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:55:59 +0200 Subject: [PATCH 03/16] multiboot2: port MemoryAreaTypeId to raw_type! as MemoryAreaTypeRaw Also lets MemoryArea::typ() return the high-level MemoryAreaType and MemoryArea::new() take impl Into. The newtype becomes #[repr(transparent)] instead of #[repr(C)] (identical layout). --- multiboot2/CHANGELOG.md | 3 + multiboot2/src/lib.rs | 2 +- multiboot2/src/memory_map.rs | 121 ++++++++--------------------------- 3 files changed, 32 insertions(+), 94 deletions(-) diff --git a/multiboot2/CHANGELOG.md b/multiboot2/CHANGELOG.md index 4155d215..dd8aeb13 100644 --- a/multiboot2/CHANGELOG.md +++ b/multiboot2/CHANGELOG.md @@ -5,6 +5,9 @@ - **Breaking:** Renamed `TagTypeId` to `TagTypeRaw`; it is now generated by the `raw_type!` macro from `multiboot2-common` and gained more conversions. `TagHeader::new()` now takes `impl Into`. +- **Breaking:** Renamed `MemoryAreaTypeId` to `MemoryAreaTypeRaw` (also + generated by `raw_type!`). `MemoryArea::typ()` now returns `MemoryAreaType` + and `MemoryArea::new()` takes `impl Into`. ## v0.26.0 / v0.26.1 (2026-08-24) diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index a1c0a0f7..851379c1 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -111,7 +111,7 @@ pub use framebuffer::{FramebufferColor, FramebufferField, FramebufferTag, Frameb pub use image_load_addr::ImageLoadPhysAddrTag; pub use memory_map::{ BasicMemoryInfoTag, EFIMemoryAreaType, EFIMemoryAttribute, EFIMemoryDesc, EFIMemoryMapTag, - MemoryArea, MemoryAreaType, MemoryAreaTypeId, MemoryMapTag, + MemoryArea, MemoryAreaType, MemoryAreaTypeRaw, MemoryMapTag, }; pub use module::{ModuleIter, ModuleTag}; pub use network::NetworkTag; diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index fbac68e6..2831001a 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -99,17 +99,17 @@ impl Tag for MemoryMapTag { pub struct MemoryArea { base_addr: u64, length: u64, - typ: MemoryAreaTypeId, + typ: MemoryAreaTypeRaw, _reserved: u32, } impl MemoryArea { /// Create a new MemoryArea. - pub fn new(base_addr: u64, length: u64, typ: impl Into) -> Self { + pub fn new(base_addr: u64, length: u64, typ: impl Into) -> Self { Self { base_addr, length, - typ: typ.into(), + typ: MemoryAreaTypeRaw::new(typ.into().val()), _reserved: 0, } } @@ -134,8 +134,8 @@ impl MemoryArea { /// The type of the memory region. #[must_use] - pub const fn typ(&self) -> MemoryAreaTypeId { - self.typ + pub const fn typ(&self) -> MemoryAreaType { + MemoryAreaType::from_val(self.typ.get()) } } @@ -149,99 +149,34 @@ impl Debug for MemoryArea { } } -/// ABI-friendly version of [`MemoryAreaType`]. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] -pub struct MemoryAreaTypeId(u32); - -impl From for MemoryAreaTypeId { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl From for u32 { - fn from(value: MemoryAreaTypeId) -> Self { - value.0 - } -} - -impl Debug for MemoryAreaTypeId { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let mt = MemoryAreaType::from(*self); - Debug::fmt(&mt, f) - } -} - -/// Abstraction over defined memory types for the memory map as well as custom -/// ones. Types 1 to 5 are defined in the Multiboot2 spec and correspond to the -/// entry types of e820 memory maps. -/// -/// This is not binary compatible with the Multiboot2 spec. Please use -/// [`MemoryAreaTypeId`] instead. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum MemoryAreaType { - /// Available memory free to be used by the OS. - Available, /* 1 */ +multiboot2_common::raw_type! { + /// ABI-friendly version of [`MemoryAreaType`]. + pub struct MemoryAreaTypeRaw(u32); - /// A reserved area that must not be used. - Reserved, /* 2, */ - - /// Usable memory holding ACPI information. - AcpiAvailable, /* 3, */ - - /// Reserved memory which needs to be preserved on hibernation. - /// Also called NVS in spec, which stands for "Non-Volatile Sleep/Storage", - /// which is part of ACPI specification. - ReservedHibernate, /* 4, */ - - /// Memory which is occupied by defective RAM modules. - Defective, /* = 5, */ - - /// Custom memory map type. - Custom(u32), -} + /// Abstraction over defined memory types for the memory map as well as + /// custom ones. Types 1 to 5 are defined in the Multiboot2 spec and + /// correspond to the entry types of e820 memory maps; other values are + /// mapped to [`MemoryAreaType::Custom`]. + /// + /// This is not binary compatible with the Multiboot2 spec. Please use + /// [`MemoryAreaTypeRaw`] instead. + pub enum MemoryAreaType { + /// Available memory free to be used by the OS. + Available = 1, -impl From for MemoryAreaType { - fn from(value: MemoryAreaTypeId) -> Self { - match value.0 { - 1 => Self::Available, - 2 => Self::Reserved, - 3 => Self::AcpiAvailable, - 4 => Self::ReservedHibernate, - 5 => Self::Defective, - val => Self::Custom(val), - } - } -} + /// A reserved area that must not be used. + Reserved = 2, -impl From for MemoryAreaTypeId { - fn from(value: MemoryAreaType) -> Self { - let integer = match value { - MemoryAreaType::Available => 1, - MemoryAreaType::Reserved => 2, - MemoryAreaType::AcpiAvailable => 3, - MemoryAreaType::ReservedHibernate => 4, - MemoryAreaType::Defective => 5, - MemoryAreaType::Custom(val) => val, - }; - integer.into() - } -} + /// Usable memory holding ACPI information. + AcpiAvailable = 3, -impl PartialEq for MemoryAreaTypeId { - fn eq(&self, other: &MemoryAreaType) -> bool { - let val: Self = (*other).into(); - let val: u32 = val.0; - self.0.eq(&val) - } -} + /// Reserved memory which needs to be preserved on hibernation. + /// Also called NVS in spec, which stands for "Non-Volatile Sleep/Storage", + /// which is part of ACPI specification. + ReservedHibernate = 4, -impl PartialEq for MemoryAreaType { - fn eq(&self, other: &MemoryAreaTypeId) -> bool { - let val: MemoryAreaTypeId = (*self).into(); - let val: u32 = val.0; - other.0.eq(&val) + /// Memory which is occupied by defective RAM modules. + Defective = 5, } } From 59be4ceb4b1fbadf574d7612c65ed5ece11ee150 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:56:59 +0200 Subject: [PATCH 04/16] multiboot2-header: use high-level MbiTagType in information request API new() now takes &[MbiTagType] and requests() returns an iterator over MbiTagType; the raw representation stays an internal storage detail. Follows the rename of MbiTagTypeId to MbiTagTypeRaw. --- multiboot2-header/CHANGELOG.md | 4 + multiboot2-header/examples/minimal.rs | 5 +- multiboot2-header/src/builder.rs | 44 ++++----- multiboot2-header/src/information_request.rs | 94 +++++++++++--------- 4 files changed, 77 insertions(+), 70 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index c1e6dad2..3d39d560 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +- **Breaking:** `InformationRequestHeaderTag::new()` now takes + `&[MbiTagType]` and `requests()` returns an iterator over `MbiTagType`. + The `MbiTagTypeId` re-export was renamed to `MbiTagTypeRaw`. + ## v0.10.0 (2026-08-24) - Fixed `Header::load` rejecting a valid header whose end tag has a non-zero diff --git a/multiboot2-header/examples/minimal.rs b/multiboot2-header/examples/minimal.rs index b9c9032a..1675a7d6 100644 --- a/multiboot2-header/examples/minimal.rs +++ b/multiboot2-header/examples/minimal.rs @@ -18,10 +18,7 @@ fn main() { )) .information_request_tag(InformationRequestHeaderTag::new( HeaderTagFlag::Required, - &[ - MbiTagType::Cmdline.into(), - MbiTagType::BootLoaderName.into(), - ], + &[MbiTagType::Cmdline, MbiTagType::BootLoaderName], )) .build(); diff --git a/multiboot2-header/src/builder.rs b/multiboot2-header/src/builder.rs index 405003ed..183f2d35 100644 --- a/multiboot2-header/src/builder.rs +++ b/multiboot2-header/src/builder.rs @@ -176,28 +176,28 @@ mod tests { .information_request_tag(InformationRequestHeaderTag::new( Optional, &[ - MbiTagType::Cmdline.into(), - MbiTagType::BootLoaderName.into(), - MbiTagType::Module.into(), - MbiTagType::BasicMeminfo.into(), - MbiTagType::Bootdev.into(), - MbiTagType::Mmap.into(), - MbiTagType::Vbe.into(), - MbiTagType::Framebuffer.into(), - MbiTagType::ElfSections.into(), - MbiTagType::Apm.into(), - MbiTagType::Efi32.into(), - MbiTagType::Efi64.into(), - MbiTagType::Smbios.into(), - MbiTagType::AcpiV1.into(), - MbiTagType::AcpiV2.into(), - MbiTagType::Network.into(), - MbiTagType::EfiMmap.into(), - MbiTagType::EfiBs.into(), - MbiTagType::Efi32Ih.into(), - MbiTagType::Efi64Ih.into(), - MbiTagType::LoadBaseAddr.into(), - MbiTagType::Custom(0x1337).into(), + MbiTagType::Cmdline, + MbiTagType::BootLoaderName, + MbiTagType::Module, + MbiTagType::BasicMeminfo, + MbiTagType::Bootdev, + MbiTagType::Mmap, + MbiTagType::Vbe, + MbiTagType::Framebuffer, + MbiTagType::ElfSections, + MbiTagType::Apm, + MbiTagType::Efi32, + MbiTagType::Efi64, + MbiTagType::Smbios, + MbiTagType::AcpiV1, + MbiTagType::AcpiV2, + MbiTagType::Network, + MbiTagType::EfiMmap, + MbiTagType::EfiBs, + MbiTagType::Efi32Ih, + MbiTagType::Efi64Ih, + MbiTagType::LoadBaseAddr, + MbiTagType::Custom(0x1337), ], )) .address_tag(AddressHeaderTag::new( diff --git a/multiboot2-header/src/information_request.rs b/multiboot2-header/src/information_request.rs index 006b1cdc..7404a72e 100644 --- a/multiboot2-header/src/information_request.rs +++ b/multiboot2-header/src/information_request.rs @@ -1,12 +1,12 @@ -use crate::{HeaderTagFlag, HeaderTagHeader}; -use crate::{HeaderTagType, MbiTagTypeRaw}; +use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; +use crate::{MbiTagType, MbiTagTypeRaw}; use core::fmt; use core::fmt::{Debug, Formatter}; #[cfg(feature = "builder")] use multiboot2_common::new_boxed; use multiboot2_common::{MaybeDynSized, Tag}; #[cfg(feature = "builder")] -use {alloc::boxed::Box, core::slice}; +use {alloc::boxed::Box, alloc::vec::Vec}; /// Specifies which tag types the bootloader should provide /// inside the mbi. @@ -21,14 +21,13 @@ impl InformationRequestHeaderTag { /// Creates a new object. #[cfg(feature = "builder")] #[must_use] - pub fn new(flags: HeaderTagFlag, requests: &[MbiTagTypeRaw]) -> Box { + pub fn new(flags: HeaderTagFlag, requests: &[MbiTagType]) -> Box { let header = HeaderTagHeader::new(HeaderTagType::InformationRequest, flags, 0); - // SAFETY: The memory we are using is valid. - let requests = unsafe { - let ptr = &raw const *requests; - slice::from_raw_parts(ptr.cast::(), size_of_val(requests)) - }; - new_boxed(header, &[requests]) + let requests = requests + .iter() + .flat_map(|request| request.val().to_ne_bytes()) + .collect::>(); + new_boxed(header, &[requests.as_slice()]) } /// Returns the [`HeaderTagType`]. @@ -49,10 +48,19 @@ impl InformationRequestHeaderTag { self.header.size() } - /// Returns the requests as array - #[must_use] - pub const fn requests(&self) -> &[MbiTagTypeRaw] { - &self.requests + /// Returns an iterator over the requested tag types. + pub fn requests(&self) -> impl Iterator + '_ { + self.requests.iter().map(|&raw| MbiTagType::from(raw)) + } +} + +/// Debug-formats the requests of an [`InformationRequestHeaderTag`] as a list +/// of [`MbiTagType`]. +struct RequestsDebug<'a>(&'a InformationRequestHeaderTag); + +impl Debug for RequestsDebug<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.0.requests()).finish() } } @@ -62,7 +70,7 @@ impl Debug for InformationRequestHeaderTag { .field("type", &self.typ()) .field("flags", &self.flags()) .field("size", &self.size()) - .field("requests", &self.requests()) + .field("requests", &RequestsDebug(self)) .finish() } } @@ -88,37 +96,35 @@ impl Tag for InformationRequestHeaderTag { #[cfg(feature = "builder")] mod tests { use super::*; - use crate::MbiTagType; #[test] fn creation() { - // Main objective here is to satisfy Miri. - let _ir = InformationRequestHeaderTag::new( - HeaderTagFlag::Optional, - &[ - MbiTagType::Cmdline.into(), - MbiTagType::BootLoaderName.into(), - MbiTagType::Module.into(), - MbiTagType::BasicMeminfo.into(), - MbiTagType::Bootdev.into(), - MbiTagType::Mmap.into(), - MbiTagType::Vbe.into(), - MbiTagType::Framebuffer.into(), - MbiTagType::ElfSections.into(), - MbiTagType::Apm.into(), - MbiTagType::Efi32.into(), - MbiTagType::Efi64.into(), - MbiTagType::Smbios.into(), - MbiTagType::AcpiV1.into(), - MbiTagType::AcpiV2.into(), - MbiTagType::Network.into(), - MbiTagType::EfiMmap.into(), - MbiTagType::EfiBs.into(), - MbiTagType::Efi32Ih.into(), - MbiTagType::Efi64Ih.into(), - MbiTagType::LoadBaseAddr.into(), - MbiTagType::Custom(0x1337).into(), - ], - ); + let requests = [ + MbiTagType::Cmdline, + MbiTagType::BootLoaderName, + MbiTagType::Module, + MbiTagType::BasicMeminfo, + MbiTagType::Bootdev, + MbiTagType::Mmap, + MbiTagType::Vbe, + MbiTagType::Framebuffer, + MbiTagType::ElfSections, + MbiTagType::Apm, + MbiTagType::Efi32, + MbiTagType::Efi64, + MbiTagType::Smbios, + MbiTagType::AcpiV1, + MbiTagType::AcpiV2, + MbiTagType::Network, + MbiTagType::EfiMmap, + MbiTagType::EfiBs, + MbiTagType::Efi32Ih, + MbiTagType::Efi64Ih, + MbiTagType::LoadBaseAddr, + MbiTagType::Custom(0x1337), + ]; + // Statement also is a good test for Miri. + let ir = InformationRequestHeaderTag::new(HeaderTagFlag::Optional, &requests); + assert!(ir.requests().eq(requests.iter().copied())); } } From 741f1c7af92991ab63e565b40d109b02710f21ae Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:57:37 +0200 Subject: [PATCH 05/16] multiboot2: fix UB on unknown framebuffer type in FramebufferTag The type field was a Rust enum with three valid discriminants, so parsing a tag with an unknown framebuffer type was undefined behavior. The field now stores the FramebufferTypeRaw newtype generated by raw_type!; the open-set FramebufferKind enum drives the dispatch in buffer_type(), which reports unknown values as UnknownFramebufferType, as before. --- multiboot2/CHANGELOG.md | 4 ++ multiboot2/src/framebuffer.rs | 117 +++++++++++++++++++--------------- multiboot2/src/lib.rs | 5 +- 3 files changed, 74 insertions(+), 52 deletions(-) diff --git a/multiboot2/CHANGELOG.md b/multiboot2/CHANGELOG.md index dd8aeb13..e75df5e8 100644 --- a/multiboot2/CHANGELOG.md +++ b/multiboot2/CHANGELOG.md @@ -8,6 +8,10 @@ - **Breaking:** Renamed `MemoryAreaTypeId` to `MemoryAreaTypeRaw` (also generated by `raw_type!`). `MemoryArea::typ()` now returns `MemoryAreaType` and `MemoryArea::new()` takes `impl Into`. +- Fixed undefined behavior when parsing a `FramebufferTag` with a framebuffer + type unknown to the specification. **Breaking:** the `FramebufferTypeId` enum + was replaced by the `FramebufferTypeRaw` newtype and the open-set + `FramebufferKind` enum; `UnknownFramebufferType` is now exported. ## v0.26.0 / v0.26.1 (2026-08-24) diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index 31029b31..6ed797c4 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -81,16 +81,12 @@ pub struct FramebufferTag { /// Contains number of bits per pixel. bpp: u8, - /// The type of framebuffer. See [`FramebufferTypeId`]. - // TODO: Strictly speaking this causes UB for invalid values. However, no - // sane bootloader puts something illegal there at the moment. When we - // refactor this (newtype pattern?), we should also streamline other - // parts in the code base accordingly. - framebuffer_type: FramebufferTypeId, + /// The type of framebuffer. See [`FramebufferKind`]. + framebuffer_type: FramebufferTypeRaw, _padding: u16, - /// This optional data and its meaning depend on the [`FramebufferTypeId`]. + /// This optional data and its meaning depends on [`FramebufferTypeRaw`]. buffer: [u8], } @@ -122,7 +118,7 @@ impl FramebufferTag { &width, &height, &[bpp], - &[buffer_type_id as u8], + &[buffer_type_id.get()], &padding, &optional_buffer, ], @@ -167,13 +163,8 @@ impl FramebufferTag { pub fn buffer_type(&self) -> Result, UnknownFramebufferType> { let mut reader = Reader::new(&self.buffer); - // TODO: We should use the newtype pattern instead or so to properly - // solve this. - let fb_type_raw = self.framebuffer_type as u8; - let fb_type = FramebufferTypeId::try_from(fb_type_raw)?; - - match fb_type { - FramebufferTypeId::Indexed => { + match FramebufferKind::from(self.framebuffer_type) { + FramebufferKind::Indexed => { // TODO we can create a struct for this and implement // DynSizedStruct for it to leverage the already existing // functionality @@ -197,7 +188,7 @@ impl FramebufferTag { }; Ok(FramebufferType::Indexed { palette }) } - FramebufferTypeId::RGB => { + FramebufferKind::RGB => { let red_pos = reader.read_next_u8(); // These refer to the bit positions of the LSB of each field let red_mask = reader.read_next_u8(); // And then the length of the field from LSB to MSB let green_pos = reader.read_next_u8(); @@ -219,7 +210,8 @@ impl FramebufferTag { }, }) } - FramebufferTypeId::Text => Ok(FramebufferType::Text), + FramebufferKind::Text => Ok(FramebufferType::Text), + FramebufferKind::Custom(val) => Err(UnknownFramebufferType(val)), } } } @@ -273,37 +265,30 @@ impl PartialEq for FramebufferTag { } } -/// ABI-compatible framebuffer type. -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(u8)] -#[expect(clippy::upper_case_acronyms)] -pub enum FramebufferTypeId { - Indexed = 0, - RGB = 1, - Text = 2, - // spec says: there may be more variants in the future -} - -impl TryFrom for FramebufferTypeId { - type Error = UnknownFramebufferType; +multiboot2_common::raw_type! { + /// ABI-compatible framebuffer type. + pub struct FramebufferTypeRaw(u8); - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::Indexed), - 1 => Ok(Self::RGB), - 2 => Ok(Self::Text), - val => Err(UnknownFramebufferType(val)), - } + /// Higher level abstraction for [`FramebufferTypeRaw`] that assigns each + /// possible value to a specific semantic according to the specification. + /// + /// Unlike [`FramebufferType`], this only describes the kind of + /// framebuffer and not its payload. + #[allow(clippy::upper_case_acronyms)] + pub enum FramebufferKind { + /// Indexed color. + Indexed = 0, + /// Direct RGB color. + RGB = 1, + /// EGA Text. + Text = 2, + // spec says: there may be more variants in the future } } -impl From> for FramebufferTypeId { +impl From> for FramebufferTypeRaw { fn from(value: FramebufferType) -> Self { - match value { - FramebufferType::Indexed { .. } => Self::Indexed, - FramebufferType::RGB { .. } => Self::RGB, - FramebufferType::Text => Self::Text, - } + value.id() } } @@ -336,13 +321,13 @@ pub enum FramebufferType<'a> { impl FramebufferType<'_> { #[must_use] - #[cfg(feature = "builder")] - const fn id(&self) -> FramebufferTypeId { - match self { - FramebufferType::Indexed { .. } => FramebufferTypeId::Indexed, - FramebufferType::RGB { .. } => FramebufferTypeId::RGB, - FramebufferType::Text => FramebufferTypeId::Text, - } + const fn id(&self) -> FramebufferTypeRaw { + let kind = match self { + FramebufferType::Indexed { .. } => FramebufferKind::Indexed, + FramebufferType::RGB { .. } => FramebufferKind::RGB, + FramebufferType::Text => FramebufferKind::Text, + }; + FramebufferTypeRaw::new(kind.val()) } #[must_use] @@ -404,7 +389,7 @@ pub struct FramebufferColor { pub blue: u8, } -/// Error when an unknown [`FramebufferTypeId`] is found. +/// Error when an unknown framebuffer type is found. #[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] #[error("Unknown framebuffer type {0}")] pub struct UnknownFramebufferType(u8); @@ -478,6 +463,36 @@ mod tests { dbg!(tag); } + /// A tag with a framebuffer type unknown to the specification must be + /// parsable without undefined behavior and report the unknown type as + /// an error. + #[test] + fn unknown_framebuffer_type_is_not_ub() { + #[rustfmt::skip] + let bytes = AlignedBytes::new([ + /* typ = framebuffer */ + 8, 0, 0, 0, + /* size = base size */ + 32, 0, 0, 0, + /* address */ + 0, 0, 0, 0, 0, 0, 0, 0, + /* pitch, width, height */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + /* bpp, type = 0x40 (unknown), padding */ + 0, 0x40, 0, 0, + ]); + let tag = GenericInfoTag::ref_from_slice(bytes.borrow()) + .unwrap() + .cast::(); + + assert_eq!(tag.buffer_type(), Err(UnknownFramebufferType(0x40))); + // The Debug implementation must also cope with unknown values. + let debug = format!("{tag:?}"); + assert!(debug.contains("UnknownFramebufferType")); + } + #[test] #[should_panic(expected = "indexed framebuffer palette must fit in the tag")] fn indexed_palette_must_fit_in_tag() { diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index 851379c1..83b22a09 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -107,7 +107,10 @@ pub use elf_sections::{ ElfSectionExt, ElfSectionFlags, ElfSectionIter, ElfSectionType, ElfSectionsTag, }; pub use end::EndTag; -pub use framebuffer::{FramebufferColor, FramebufferField, FramebufferTag, FramebufferType}; +pub use framebuffer::{ + FramebufferColor, FramebufferField, FramebufferKind, FramebufferTag, FramebufferType, + FramebufferTypeRaw, UnknownFramebufferType, +}; pub use image_load_addr::ImageLoadPhysAddrTag; pub use memory_map::{ BasicMemoryInfoTag, EFIMemoryAreaType, EFIMemoryAttribute, EFIMemoryDesc, EFIMemoryMapTag, From 40b2347c3f579d09d833b86c486ab7b130ca2924 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:58:05 +0200 Subject: [PATCH 06/16] multiboot2: fix UB on unknown memory model in VBEModeInfo The memory_model field was a Rust enum with eight valid discriminants, while the VBE spec reserves 0x08..=0x0F and assigns 0x10..=0xFF to the OEM. The field is now typed as the VBEMemoryModelRaw newtype generated by raw_type!; VBEMemoryModel gained a Custom variant. --- multiboot2/CHANGELOG.md | 4 ++ multiboot2/src/lib.rs | 2 +- multiboot2/src/vbe_info.rs | 77 ++++++++++++++++++++++++++++++-------- 3 files changed, 67 insertions(+), 16 deletions(-) diff --git a/multiboot2/CHANGELOG.md b/multiboot2/CHANGELOG.md index e75df5e8..e8697e0c 100644 --- a/multiboot2/CHANGELOG.md +++ b/multiboot2/CHANGELOG.md @@ -12,6 +12,10 @@ type unknown to the specification. **Breaking:** the `FramebufferTypeId` enum was replaced by the `FramebufferTypeRaw` newtype and the open-set `FramebufferKind` enum; `UnknownFramebufferType` is now exported. +- Fixed undefined behavior when reading a `VBEModeInfo` with a reserved or + OEM-defined memory model. **Breaking:** the `memory_model` field is now + typed as the new `VBEMemoryModelRaw` newtype; `VBEMemoryModel` gained a + `Custom` variant. ## v0.26.0 / v0.26.1 (2026-08-24) diff --git a/multiboot2/src/lib.rs b/multiboot2/src/lib.rs index 83b22a09..c3db6997 100644 --- a/multiboot2/src/lib.rs +++ b/multiboot2/src/lib.rs @@ -126,7 +126,7 @@ pub use tag_type::{TagType, TagTypeRaw}; pub use util::{StringError, parse_slice_as_string}; pub use vbe_info::{ VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag, - VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes, + VBEMemoryModel, VBEMemoryModelRaw, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes, }; /// Magic number that a Multiboot2-compliant bootloader will use to identify diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index 2d22d0ba..43db572e 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -228,7 +228,7 @@ pub struct VBEModeInfo { pub number_of_banks: u8, /// Memory model type - pub memory_model: VBEMemoryModel, + pub memory_model: VBEMemoryModelRaw, /// Bank size (Measured in Kilobytes.) pub bank_size: u8, @@ -443,18 +443,65 @@ bitflags! { } } -/// The MemoryModel field specifies the general type of memory organization used in modes. -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(u8)] -#[expect(missing_docs)] -pub enum VBEMemoryModel { - #[default] - Text = 0x00, - CGAGraphics = 0x01, - HerculesGraphics = 0x02, - Planar = 0x03, - PackedPixel = 0x04, - Unchained = 0x05, - DirectColor = 0x06, - YUV = 0x07, +multiboot2_common::raw_type! { + /// ABI-compatible form of [`VBEMemoryModel`] that matches the binary + /// representation (`u8`). + #[derive(Default)] + pub struct VBEMemoryModelRaw(u8); + + /// The MemoryModel field specifies the general type of memory organization used in modes. + /// + /// Values `0x08..=0x0F` are reserved and values `0x10..=0xFF` are defined + /// by the OEM according to the VBE specification. Both are mapped to + /// [`VBEMemoryModel::Custom`]. + #[derive(Default)] + #[allow(clippy::upper_case_acronyms)] + pub enum VBEMemoryModel { + /// Text mode. + #[default] + Text = 0x00, + /// CGA graphics. + CGAGraphics = 0x01, + /// Hercules graphics. + HerculesGraphics = 0x02, + /// Planar. + Planar = 0x03, + /// Packed pixel. + PackedPixel = 0x04, + /// Non-chain 4, 256 color. + Unchained = 0x05, + /// Direct color. + DirectColor = 0x06, + /// YUV. + YUV = 0x07, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The sizes of the structs given in the VBE specification. + #[test] + fn test_layout() { + assert_eq!(size_of::(), 512); + assert_eq!(size_of::(), 256); + } + + /// A mode info block with a memory model unknown to the specification + /// must be readable without undefined behavior. + #[test] + fn unknown_memory_model_is_not_ub() { + let mut bytes = [0_u8; size_of::()]; + // Offset of `memory_model` within the packed struct. + bytes[27] = 0x42; + + // SAFETY: The struct is `repr(C, packed)`, one byte aligned, and + // since the fix every bit pattern is a valid instance. + let mode_info = unsafe { core::ptr::read_unaligned(bytes.as_ptr().cast::()) }; + + assert_eq!(mode_info.memory_model, VBEMemoryModel::Custom(0x42)); + let debug = format!("{mode_info:?}"); + assert!(debug.contains("Custom(66)")); + } } From 9e1d498e7641ff4da2f31de9684d755f415f9d46 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:58:41 +0200 Subject: [PATCH 07/16] multiboot2-header: fix UB on unknown header tag type The tag type was a Rust enum with eleven valid discriminants and is read for every tag during iteration of raw memory, so any custom or future tag type was undefined behavior. HeaderTagHeader now stores the HeaderTagTypeRaw newtype generated by raw_type!; the typ() getters keep returning HeaderTagType, which gained a Custom variant. The unused HeaderTagType::count() was removed. --- multiboot2-header/CHANGELOG.md | 5 +++ multiboot2-header/src/header.rs | 30 ++++++++++++- multiboot2-header/src/tags.rs | 80 ++++++++++++++++----------------- 3 files changed, 73 insertions(+), 42 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index 3d39d560..0476bce0 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -5,6 +5,11 @@ - **Breaking:** `InformationRequestHeaderTag::new()` now takes `&[MbiTagType]` and `requests()` returns an iterator over `MbiTagType`. The `MbiTagTypeId` re-export was renamed to `MbiTagTypeRaw`. +- Fixed undefined behavior when parsing a header containing a tag with a type + unknown to the specification. `HeaderTagHeader` now stores the new + `HeaderTagTypeRaw` newtype; the `typ()` getters keep returning + `HeaderTagType`, which gained a `Custom` variant. **Breaking:** the now + meaningless `HeaderTagType::count()` was removed. ## v0.10.0 (2026-08-24) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 5245acdf..cb67bfd0 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -78,7 +78,7 @@ impl<'a> Header<'a> { // The spec only requires the end tag to have type 0 and size 8; it // does not constrain the flags field. - typ == HeaderTagType::End as u16 && size == size_of::() + typ == HeaderTagType::End.val() && size == size_of::() }) } @@ -552,6 +552,32 @@ mod tests { assert!(header.is_ok()); } + /// A header containing a tag with a type unknown to the specification + /// must be parsable and iterable without undefined behavior. + #[test] + fn load_accepts_unknown_tag_type() { + let mut bytes = AlignedBytes::new([0; 32]); + let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32); + bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes()); + bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes()); + bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes()); + // Tag with a type unknown to the specification. + bytes.0[16..18].copy_from_slice(&0x1337_u16.to_le_bytes()); + bytes.0[20..24].copy_from_slice(&8_u32.to_le_bytes()); + // End tag. + bytes.0[28..32].copy_from_slice(&8_u32.to_le_bytes()); + + // SAFETY: The test buffer is aligned and contains a valid + // header layout. + let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap(); + + let tag = header.iter().next().unwrap(); + assert_eq!(tag.header().typ(), HeaderTagType::Custom(0x1337)); + // The Debug implementation must also cope with unknown types. + let debug = format!("{header:?}"); + assert!(debug.contains("Custom(4919)")); + } + #[test] fn load_rejects_missing_end_tag() { let mut bytes = AlignedBytes::new([0; 16]); @@ -575,7 +601,7 @@ mod tests { bytes.0[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes()); bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes()); bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes()); - bytes.0[16..18].copy_from_slice(&(HeaderTagType::InformationRequest as u16).to_le_bytes()); + bytes.0[16..18].copy_from_slice(&HeaderTagType::InformationRequest.val().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 diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index f330214f..b9017b04 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -17,43 +17,39 @@ pub enum HeaderTagISA { MIPS32 = 4, } -/// Possible types for header tags of a Multiboot2 header. -/// -/// The names and values are taken from the example C code at the bottom of the -/// Multiboot2 specification. This value stands in the `typ` property of -/// [`HeaderTagHeader`]. -#[repr(u16)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum HeaderTagType { - /// Type for [`crate::EndHeaderTag`]. - End = 0, - /// Type for [`crate::InformationRequestHeaderTag`]. - InformationRequest = 1, - /// Type for [`crate::AddressHeaderTag`]. - Address = 2, - /// Type for [`crate::EntryAddressHeaderTag`]. - EntryAddress = 3, - /// Type for [`crate::ConsoleHeaderTag`]. - ConsoleFlags = 4, - /// Type for [`crate::FramebufferHeaderTag`]. - Framebuffer = 5, - /// Type for [`crate::ModuleAlignHeaderTag`]. - ModuleAlign = 6, - /// Type for [`crate::EfiBootServiceHeaderTag`]. - EfiBS = 7, - /// Type for [`crate::EntryEfi32HeaderTag`]. - EntryAddressEFI32 = 8, - /// Type for [`crate::EntryEfi64HeaderTag`]. - EntryAddressEFI64 = 9, - /// Type for [`crate::RelocatableHeaderTag`]. - Relocatable = 10, -} +multiboot2_common::raw_type! { + /// Serialized form of [`HeaderTagType`] that matches the binary + /// representation (`u16`). This value stands in the `typ` property of + /// [`HeaderTagHeader`]. + pub struct HeaderTagTypeRaw(u16); -impl HeaderTagType { - /// Returns the number of possible variants. - #[must_use] - pub const fn count() -> u32 { - 11 + /// Possible types for header tags of a Multiboot2 header. + /// + /// The names and values are taken from the example C code at the bottom of + /// the Multiboot2 specification. + pub enum HeaderTagType { + /// Type for [`crate::EndHeaderTag`]. + End = 0, + /// Type for [`crate::InformationRequestHeaderTag`]. + InformationRequest = 1, + /// Type for [`crate::AddressHeaderTag`]. + Address = 2, + /// Type for [`crate::EntryAddressHeaderTag`]. + EntryAddress = 3, + /// Type for [`crate::ConsoleHeaderTag`]. + ConsoleFlags = 4, + /// Type for [`crate::FramebufferHeaderTag`]. + Framebuffer = 5, + /// Type for [`crate::ModuleAlignHeaderTag`]. + ModuleAlign = 6, + /// Type for [`crate::EfiBootServiceHeaderTag`]. + EfiBS = 7, + /// Type for [`crate::EntryEfi32HeaderTag`]. + EntryAddressEFI32 = 8, + /// Type for [`crate::EntryEfi64HeaderTag`]. + EntryAddressEFI64 = 9, + /// Type for [`crate::RelocatableHeaderTag`]. + Relocatable = 10, } } @@ -73,8 +69,8 @@ pub enum HeaderTagFlag { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C, align(8))] pub struct HeaderTagHeader { - typ: HeaderTagType, /* u16 */ - flags: HeaderTagFlag, /* u16 */ + typ: HeaderTagTypeRaw, /* u16 */ + flags: HeaderTagFlag, /* u16 */ size: u32, // Followed by optional additional tag-specific fields. } @@ -83,13 +79,17 @@ impl HeaderTagHeader { /// Creates a new header. #[must_use] pub const fn new(typ: HeaderTagType, flags: HeaderTagFlag, size: u32) -> Self { - Self { typ, flags, size } + Self { + typ: HeaderTagTypeRaw::new(typ.val()), + flags, + size, + } } /// Returns the [`HeaderTagType`]. #[must_use] pub const fn typ(&self) -> HeaderTagType { - self.typ + HeaderTagType::from_val(self.typ.get()) } /// Returns the [`HeaderTagFlag`]s. From a1362306dccde199042784cfe12fba619cdfbbb5 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:59:11 +0200 Subject: [PATCH 08/16] multiboot2-header: fix UB on unknown architecture in basic header The arch field was the HeaderTagISA enum with two valid discriminants (0 and 4), so loading an image with any other architecture value was undefined behavior. The field now stores the HeaderTagISARaw newtype generated by raw_type!; the arch() getters keep returning HeaderTagISA, which gained a Custom variant. --- multiboot2-header/CHANGELOG.md | 4 ++++ multiboot2-header/src/header.rs | 41 +++++++++++++++++++++++++-------- multiboot2-header/src/tags.rs | 27 +++++++++++++--------- 3 files changed, 51 insertions(+), 21 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index 0476bce0..60ccd666 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -10,6 +10,10 @@ `HeaderTagTypeRaw` newtype; the `typ()` getters keep returning `HeaderTagType`, which gained a `Custom` variant. **Breaking:** the now meaningless `HeaderTagType::count()` was removed. +- Fixed undefined behavior when parsing a header with an architecture unknown + to the specification. `Multiboot2BasicHeader` now stores the new + `HeaderTagISARaw` newtype; the `arch()` getters keep returning + `HeaderTagISA`, which gained a `Custom` variant. ## v0.10.0 (2026-08-24) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index cb67bfd0..d9f78377 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -1,8 +1,8 @@ use crate::{ AddressHeaderTag, ConsoleHeaderTag, EfiBootServiceHeaderTag, EntryAddressHeaderTag, EntryEfi32HeaderTag, EntryEfi64HeaderTag, FramebufferHeaderTag, HeaderTagHeader, HeaderTagISA, - HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, RelocatableHeaderTag, - TagIter, + HeaderTagISARaw, HeaderTagType, InformationRequestHeaderTag, ModuleAlignHeaderTag, + RelocatableHeaderTag, TagIter, }; use core::fmt::{Debug, Formatter}; use core::ptr::NonNull; @@ -355,7 +355,7 @@ pub enum LoadError { pub struct Multiboot2BasicHeader { /// Must be the value of [`MAGIC`]. header_magic: u32, - arch: HeaderTagISA, + arch: HeaderTagISARaw, length: u32, checksum: u32, // Followed by dynamic amount of dynamically sized header tags. @@ -370,7 +370,7 @@ impl Multiboot2BasicHeader { let checksum = Self::calc_checksum(magic, arch, length); Self { header_magic: magic, - arch, + arch: HeaderTagISARaw::new(arch.val()), length, checksum, } @@ -386,7 +386,7 @@ impl Multiboot2BasicHeader { u32, /* expected checksum */ ), > { - let check = Self::calc_checksum(self.header_magic, self.arch, self.length); + let check = Self::calc_checksum(self.header_magic, self.arch(), self.length); if check == self.checksum { Ok(()) } else { @@ -397,7 +397,7 @@ impl Multiboot2BasicHeader { /// Calculates the checksum as described in the spec. #[must_use] pub const fn calc_checksum(magic: u32, arch: HeaderTagISA, length: u32) -> u32 { - (0x100000000 - magic as u64 - arch as u64 - length as u64) as u32 + (0x100000000 - magic as u64 - arch.val() as u64 - length as u64) as u32 } /// Returns the header magic. @@ -409,7 +409,7 @@ impl Multiboot2BasicHeader { /// Returns the [`HeaderTagISA`]. #[must_use] pub const fn arch(&self) -> HeaderTagISA { - self.arch + HeaderTagISA::from_val(self.arch.get()) } /// Returns the length. @@ -432,7 +432,7 @@ impl DynSizedHeader for Multiboot2BasicHeader { fn set_size(&mut self, total_size: usize) { self.length = total_size as u32; - self.checksum = Self::calc_checksum(self.header_magic, self.arch, total_size as u32); + self.checksum = Self::calc_checksum(self.header_magic, self.arch(), total_size as u32); } } @@ -461,7 +461,7 @@ mod tests { // Aligned magic buffer[0..4].copy_from_slice(&MAGIC.to_le_bytes()); // Architecture - buffer[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes()); + buffer[4..8].copy_from_slice(&HeaderTagISA::I386.val().to_le_bytes()); // Total size buffer[8..12].copy_from_slice(&24_u32.to_le_bytes()); // Checksum @@ -552,6 +552,27 @@ mod tests { assert!(header.is_ok()); } + /// A header with an architecture unknown to the specification must be + /// parsable without undefined behavior. + #[test] + fn load_accepts_unknown_architecture() { + let mut bytes = AlignedBytes::new([0; 24]); + write_minimal_valid_header_tag(&mut bytes.0); + // Architecture unknown to the specification. + bytes.0[4..8].copy_from_slice(&3_u32.to_le_bytes()); + let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::Custom(3), 24); + 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 { Header::load(bytes.as_ptr().cast()) }.unwrap(); + + assert_eq!(header.arch(), HeaderTagISA::Custom(3)); + // The Debug implementation must also cope with unknown values. + let debug = format!("{header:?}"); + assert!(debug.contains("Custom(3)")); + } + /// A header containing a tag with a type unknown to the specification /// must be parsable and iterable without undefined behavior. #[test] @@ -598,7 +619,7 @@ mod tests { let mut bytes = AlignedBytes::new([0; 32]); let checksum = Multiboot2BasicHeader::calc_checksum(MAGIC, HeaderTagISA::I386, 32); bytes.0[0..4].copy_from_slice(&MAGIC.to_le_bytes()); - bytes.0[4..8].copy_from_slice(&(HeaderTagISA::I386 as u32).to_le_bytes()); + bytes.0[4..8].copy_from_slice(&HeaderTagISA::I386.val().to_le_bytes()); bytes.0[8..12].copy_from_slice(&32_u32.to_le_bytes()); bytes.0[12..16].copy_from_slice(&checksum.to_le_bytes()); bytes.0[16..18].copy_from_slice(&HeaderTagType::InformationRequest.val().to_le_bytes()); diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index b9017b04..598b9130 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -4,17 +4,22 @@ use multiboot2_common::Header; -/// ISA/ARCH in Multiboot2 header. -#[repr(u32)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum HeaderTagISA { - /// Spec: "means 32-bit (protected) mode of i386". - /// Caution: This is confusing. If you use the EFI64-tag - /// on an UEFI system, the machine will boot into `64-bit long mode`. - /// Therefore this tag should be understood as "arch=x86|x86_64". - I386 = 0, - /// 32-bit MIPS - MIPS32 = 4, +multiboot2_common::raw_type! { + /// Serialized form of [`HeaderTagISA`] that matches the binary + /// representation (`u32`). This value stands in the `arch` property of + /// [`crate::Multiboot2BasicHeader`]. + pub struct HeaderTagISARaw(u32); + + /// ISA/ARCH in Multiboot2 header. + pub enum HeaderTagISA { + /// Spec: "means 32-bit (protected) mode of i386". + /// Caution: This is confusing. If you use the EFI64-tag + /// on an UEFI system, the machine will boot into `64-bit long mode`. + /// Therefore this tag should be understood as "arch=x86|x86_64". + I386 = 0, + /// 32-bit MIPS + MIPS32 = 4, + } } multiboot2_common::raw_type! { From a16abfcb220d2941f9c0a36d374ecefc7c0d419b Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 17:59:36 +0200 Subject: [PATCH 09/16] multiboot2-header: fix UB on unknown preference in RelocatableHeaderTag The preference field was a Rust enum with three valid discriminants, so parsing a tag with any other value was undefined behavior. The tag now stores the RelocatableHeaderTagPreferenceRaw newtype generated by raw_type!; preference() keeps returning the high-level enum, which gained a Custom variant. --- multiboot2-header/CHANGELOG.md | 4 ++ multiboot2-header/src/relocatable.rs | 68 +++++++++++++++++++++------- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index 60ccd666..d486fb31 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -14,6 +14,10 @@ to the specification. `Multiboot2BasicHeader` now stores the new `HeaderTagISARaw` newtype; the `arch()` getters keep returning `HeaderTagISA`, which gained a `Custom` variant. +- Fixed undefined behavior when parsing a `RelocatableHeaderTag` with a + placement preference unknown to the specification. The tag now stores the + new `RelocatableHeaderTagPreferenceRaw` newtype; `preference()` keeps + returning `RelocatableHeaderTagPreference`, which gained a `Custom` variant. ## v0.10.0 (2026-08-24) diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index 5fba477a..75b6946f 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -3,18 +3,22 @@ use core::fmt; use core::fmt::{Debug, Formatter}; use multiboot2_common::{MaybeDynSized, Tag}; -/// Specifies the bootloader's preferred placement for a relocatable image. -#[repr(u32)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum RelocatableHeaderTagPreference { - /// Let the bootloader choose the image location. - None = 0, - /// Load the image at the lowest possible address that is not below - /// `min_addr`. - Low = 1, - /// Load the image at the highest possible address that does not end above - /// `max_addr`. - High = 2, +multiboot2_common::raw_type! { + /// Serialized form of [`RelocatableHeaderTagPreference`] that matches the + /// binary representation (`u32`). + pub struct RelocatableHeaderTagPreferenceRaw(u32); + + /// Specifies the bootloader's preferred placement for a relocatable image. + pub enum RelocatableHeaderTagPreference { + /// Let the bootloader choose the image location. + None = 0, + /// Load the image at the lowest possible address that is not below + /// `min_addr`. + Low = 1, + /// Load the image at the highest possible address that does not end above + /// `max_addr`. + High = 2, + } } /// This tag indicates that the image is relocatable. @@ -32,7 +36,7 @@ pub struct RelocatableHeaderTag { max_addr: u32, /// Image alignment in memory, e.g. 4096. align: u32, - preference: RelocatableHeaderTagPreference, + preference: RelocatableHeaderTagPreferenceRaw, } impl RelocatableHeaderTag { @@ -52,7 +56,7 @@ impl RelocatableHeaderTag { min_addr, max_addr, align, - preference, + preference: RelocatableHeaderTagPreferenceRaw::new(preference.val()), } } @@ -95,7 +99,7 @@ impl RelocatableHeaderTag { /// Return the preference. #[must_use] pub const fn preference(&self) -> RelocatableHeaderTagPreference { - self.preference + RelocatableHeaderTagPreference::from_val(self.preference.get()) } } @@ -127,10 +131,42 @@ impl Tag for RelocatableHeaderTag { #[cfg(test)] mod tests { - use crate::RelocatableHeaderTag; + use super::*; + use crate::GenericHeaderTag; + use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; #[test] fn test_assert_size() { assert_eq!(size_of::(), 2 + 2 + 4 + 4 + 4 + 4 + 4); } + + /// A tag with a placement preference unknown to the specification must + /// be parsable without undefined behavior. + #[test] + fn unknown_preference_is_not_ub() { + #[rustfmt::skip] + let bytes = AlignedBytes::new([ + /* typ = relocatable */ + 10, 0, + /* flags */ + 0, 0, + /* size */ + 24, 0, 0, 0, + /* min_addr, max_addr, align */ + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + /* preference = 99 (unknown) */ + 99, 0, 0, 0, + ]); + let tag = GenericHeaderTag::ref_from_slice(bytes.borrow()) + .unwrap() + .cast::(); + + assert_eq!(tag.preference(), RelocatableHeaderTagPreference::Custom(99)); + // The Debug implementation must also cope with unknown values. + let debug = format!("{tag:?}"); + assert!(debug.contains("Custom(99)")); + } } From 69aff74ed51e5c7fe58f0611e68d19d0ffaf4e43 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 18:00:03 +0200 Subject: [PATCH 10/16] multiboot2-header: fix UB on unknown flags value in header tags The flags field of every header tag was the HeaderTagFlag enum with two valid discriminants, so parsing a tag with any other value was undefined behavior. HeaderTagHeader now stores the HeaderTagFlagRaw newtype generated by raw_type!; the flags() getters keep returning HeaderTagFlag, which gained a Custom variant. --- multiboot2-header/CHANGELOG.md | 4 ++++ multiboot2-header/src/header.rs | 19 +++++++++++++++++++ multiboot2-header/src/tags.rs | 31 ++++++++++++++++++------------- 3 files changed, 41 insertions(+), 13 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index d486fb31..e3a5ab22 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -18,6 +18,10 @@ placement preference unknown to the specification. The tag now stores the new `RelocatableHeaderTagPreferenceRaw` newtype; `preference()` keeps returning `RelocatableHeaderTagPreference`, which gained a `Custom` variant. +- Fixed undefined behavior when parsing a header tag whose flags field holds a + value other than 0 or 1. `HeaderTagHeader` now stores the new + `HeaderTagFlagRaw` newtype; the `flags()` getters keep returning + `HeaderTagFlag`, which gained a `Custom` variant. ## v0.10.0 (2026-08-24) diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index d9f78377..41c02065 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -552,6 +552,25 @@ mod tests { assert!(header.is_ok()); } + /// A tag with a flags value unknown to the specification must be + /// parsable without undefined behavior, preserving the value. + #[test] + fn load_accepts_unknown_flags_value() { + use crate::HeaderTagFlag; + + let mut bytes = AlignedBytes::new([0; 24]); + write_minimal_valid_header_tag(&mut bytes.0); + // End tag: flags value without a specified meaning. + bytes.0[18..20].copy_from_slice(&0xFFFF_u16.to_le_bytes()); + + // SAFETY: The test buffer is aligned and contains a valid + // header layout. + let header = unsafe { Header::load(bytes.as_ptr().cast()) }.unwrap(); + + let flags = header.iter().next().unwrap().header().flags(); + assert_eq!(flags, HeaderTagFlag::Custom(0xFFFF)); + } + /// A header with an architecture unknown to the specification must be /// parsable without undefined behavior. #[test] diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index 598b9130..fa59a90a 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -58,15 +58,20 @@ multiboot2_common::raw_type! { } } -/// Flags for Multiboot2 header tags. -#[repr(u16)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum HeaderTagFlag { - /// The bootloader must provide this tag. If this is not possible, the - /// bootloader will fail to load the kernel. - Required = 0, - /// The bootloader should provide the tag if possible. - Optional = 1, +multiboot2_common::raw_type! { + /// Serialized form of [`HeaderTagFlag`] that matches the binary + /// representation (`u16`). This value stands in the `flags` property of + /// [`HeaderTagHeader`]. + pub struct HeaderTagFlagRaw(u16); + + /// Flags for Multiboot2 header tags. + pub enum HeaderTagFlag { + /// The bootloader must provide this tag. If this is not possible, the + /// bootloader will fail to load the kernel. + Required = 0, + /// The bootloader should provide the tag if possible. + Optional = 1, + } } /// The common header that all header tags share. Specific tags may have @@ -74,8 +79,8 @@ pub enum HeaderTagFlag { #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] #[repr(C, align(8))] pub struct HeaderTagHeader { - typ: HeaderTagTypeRaw, /* u16 */ - flags: HeaderTagFlag, /* u16 */ + typ: HeaderTagTypeRaw, /* u16 */ + flags: HeaderTagFlagRaw, /* u16 */ size: u32, // Followed by optional additional tag-specific fields. } @@ -86,7 +91,7 @@ impl HeaderTagHeader { pub const fn new(typ: HeaderTagType, flags: HeaderTagFlag, size: u32) -> Self { Self { typ: HeaderTagTypeRaw::new(typ.val()), - flags, + flags: HeaderTagFlagRaw::new(flags.val()), size, } } @@ -100,7 +105,7 @@ impl HeaderTagHeader { /// Returns the [`HeaderTagFlag`]s. #[must_use] pub const fn flags(&self) -> HeaderTagFlag { - self.flags + HeaderTagFlag::from_val(self.flags.get()) } /// Returns the size. From 53c43f129287bca899f29427cd87f63622583719 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Tue, 25 Aug 2026 18:00:32 +0200 Subject: [PATCH 11/16] multiboot2-header: fix UB and spec violation in ConsoleHeaderTagFlags The enum discriminants did not match the specification: per the example C code, console-required is value 1 and EGA text support is value 2, but the enum serialized them as 0 and 1. A bootloader therefore read a ConsoleRequired tag as "no console required", and parsing any other value was undefined behavior. The tag now stores the ConsoleHeaderTagFlagsRaw newtype generated by raw_type!, and the enum carries the spec-mandated values plus a Custom variant. --- multiboot2-header/CHANGELOG.md | 6 +++ multiboot2-header/src/console.rs | 71 +++++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 11 deletions(-) diff --git a/multiboot2-header/CHANGELOG.md b/multiboot2-header/CHANGELOG.md index e3a5ab22..df07893a 100644 --- a/multiboot2-header/CHANGELOG.md +++ b/multiboot2-header/CHANGELOG.md @@ -22,6 +22,12 @@ value other than 0 or 1. `HeaderTagHeader` now stores the new `HeaderTagFlagRaw` newtype; the `flags()` getters keep returning `HeaderTagFlag`, which gained a `Custom` variant. +- Fixed undefined behavior and a spec violation in `ConsoleHeaderTagFlags`: + the enum discriminants did not match the specification. **Breaking:** + `ConsoleRequired` now serializes to `1` (was `0`) and `EgaTextSupported` to + `2` (was `1`), matching the example C code of the specification; the enum + gained a `Custom` variant and the tag stores the new + `ConsoleHeaderTagFlagsRaw` newtype. ## v0.10.0 (2026-08-24) diff --git a/multiboot2-header/src/console.rs b/multiboot2-header/src/console.rs index 3f649420..9da53ec9 100644 --- a/multiboot2-header/src/console.rs +++ b/multiboot2-header/src/console.rs @@ -1,14 +1,23 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use multiboot2_common::{MaybeDynSized, Tag}; -/// Possible flags for [`ConsoleHeaderTag`]. -#[repr(u32)] -#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum ConsoleHeaderTagFlags { - /// Console required. - ConsoleRequired = 0, - /// EGA text support. - EgaTextSupported = 1, +multiboot2_common::raw_type! { + /// Serialized form of [`ConsoleHeaderTagFlags`] that matches the binary + /// representation (`u32`). + pub struct ConsoleHeaderTagFlagsRaw(u32); + + /// Possible flags for [`ConsoleHeaderTag`]. + /// + /// The values are taken from the example C code at the bottom of the + /// Multiboot2 specification. + pub enum ConsoleHeaderTagFlags { + /// At least one of the consoles supported by the bootloader must be + /// present and information about it must be available in the boot + /// information. + ConsoleRequired = 1, + /// The OS image has EGA text support. + EgaTextSupported = 2, + } } /// Tells that a console must be available in MBI. @@ -17,7 +26,7 @@ pub enum ConsoleHeaderTagFlags { #[repr(C, align(8))] pub struct ConsoleHeaderTag { header: HeaderTagHeader, - console_flags: ConsoleHeaderTagFlags, + console_flags: ConsoleHeaderTagFlagsRaw, } impl ConsoleHeaderTag { @@ -28,7 +37,7 @@ impl ConsoleHeaderTag { HeaderTagHeader::new(HeaderTagType::ConsoleFlags, flags, Self::BASE_SIZE as u32); Self { header, - console_flags, + console_flags: ConsoleHeaderTagFlagsRaw::new(console_flags.val()), } } @@ -53,7 +62,7 @@ impl ConsoleHeaderTag { /// Returns the [`ConsoleHeaderTagFlags`]. #[must_use] pub const fn console_flags(&self) -> ConsoleHeaderTagFlags { - self.console_flags + ConsoleHeaderTagFlags::from_val(self.console_flags.get()) } } @@ -67,3 +76,43 @@ impl Tag for ConsoleHeaderTag { type IDType = HeaderTagType; const ID: HeaderTagType = HeaderTagType::ConsoleFlags; } + +#[cfg(test)] +mod tests { + use super::*; + use crate::GenericHeaderTag; + use core::borrow::Borrow; + use multiboot2_common::test_utils::AlignedBytes; + + /// The console flag values must match the example C code of the + /// specification. + #[test] + fn console_flags_match_spec_values() { + assert_eq!(ConsoleHeaderTagFlags::ConsoleRequired.val(), 1); + assert_eq!(ConsoleHeaderTagFlags::EgaTextSupported.val(), 2); + } + + /// A tag with a console flags value unknown to the specification must be + /// parsable without undefined behavior. + #[test] + fn unknown_console_flags_are_not_ub() { + #[rustfmt::skip] + let bytes = AlignedBytes::new([ + /* typ = console flags */ + 4, 0, + /* flags */ + 0, 0, + /* size */ + 12, 0, 0, 0, + /* console_flags = 3 (unknown) */ + 3, 0, 0, 0, + /* padding to alignment */ + 0, 0, 0, 0, + ]); + let tag = GenericHeaderTag::ref_from_slice(bytes.borrow()) + .unwrap() + .cast::(); + + assert_eq!(tag.console_flags(), ConsoleHeaderTagFlags::Custom(3)); + } +} From aeaf66b8b36cf8045bc92f825c0992b5cded22e1 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Aug 2026 07:04:12 +0200 Subject: [PATCH 12/16] treewide: assert type sizes at compile time Replaces the test_assert_size()/test_layout()-style unit tests with const assertions placed right after the respective type definitions. This checks the layout on every build instead of only when running the test suite. --- multiboot2-common/src/raw.rs | 9 +++------ multiboot2-common/src/test_utils.rs | 6 ++---- multiboot2-header/src/address.rs | 12 ++---------- multiboot2-header/src/end.rs | 12 ++---------- multiboot2-header/src/header.rs | 7 ++----- multiboot2-header/src/module_align.rs | 12 ++---------- multiboot2-header/src/relocatable.rs | 7 ++----- multiboot2-header/src/tags.rs | 12 ++---------- multiboot2-header/src/uefi_bs.rs | 12 ++---------- multiboot2/src/framebuffer.rs | 8 ++------ multiboot2/src/vbe_info.rs | 11 ++++------- 11 files changed, 25 insertions(+), 83 deletions(-) diff --git a/multiboot2-common/src/raw.rs b/multiboot2-common/src/raw.rs index 874b2df7..9ba8e42d 100644 --- a/multiboot2-common/src/raw.rs +++ b/multiboot2-common/src/raw.rs @@ -225,12 +225,9 @@ mod tests { } } - /// The newtype must be binary compatible with the underlying integer. - #[test] - fn test_layout() { - assert_eq!(size_of::(), size_of::()); - assert_eq!(align_of::(), align_of::()); - } + // The newtype must be binary compatible with the underlying integer. + const _: () = assert!(size_of::() == size_of::()); + const _: () = assert!(align_of::() == align_of::()); #[test] fn test_const_constructors_and_getters() { diff --git a/multiboot2-common/src/test_utils.rs b/multiboot2-common/src/test_utils.rs index c534eefc..5cd9d4c1 100644 --- a/multiboot2-common/src/test_utils.rs +++ b/multiboot2-common/src/test_utils.rs @@ -15,6 +15,8 @@ use core::ops::Deref; #[repr(C, align(8))] pub struct AlignedBytes(pub [u8; N]); +const _: () = assert!(align_of::>() == crate::ALIGNMENT); + impl AlignedBytes { /// Creates a new type. #[must_use] @@ -116,14 +118,10 @@ impl Tag for DummyDstTag { #[cfg(test)] mod tests { - use crate::ALIGNMENT; - use super::*; #[test] fn abi() { - assert_eq!(align_of::>(), ALIGNMENT); - let bytes = AlignedBytes([0]); assert_eq!(bytes.as_ptr().align_offset(8), 0); assert_eq!((&raw const bytes[0]).align_offset(8), 0); diff --git a/multiboot2-header/src/address.rs b/multiboot2-header/src/address.rs index 50f54ff1..543423cb 100644 --- a/multiboot2-header/src/address.rs +++ b/multiboot2-header/src/address.rs @@ -42,6 +42,8 @@ pub struct AddressHeaderTag { bss_end_addr: u32, } +const _: () = assert!(size_of::() == 2 + 2 + 4 + 4 + 4 + 4 + 4); + impl AddressHeaderTag { /// Constructs a new tag. #[must_use] @@ -115,13 +117,3 @@ impl Tag for AddressHeaderTag { type IDType = HeaderTagType; const ID: HeaderTagType = HeaderTagType::Address; } - -#[cfg(test)] -mod tests { - use crate::AddressHeaderTag; - - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4 + 4 + 4 + 4 + 4); - } -} diff --git a/multiboot2-header/src/end.rs b/multiboot2-header/src/end.rs index 42c1f7e5..a868d0d2 100644 --- a/multiboot2-header/src/end.rs +++ b/multiboot2-header/src/end.rs @@ -8,6 +8,8 @@ pub struct EndHeaderTag { header: HeaderTagHeader, } +const _: () = assert!(size_of::() == 2 + 2 + 4); + impl Default for EndHeaderTag { fn default() -> Self { Self::new() @@ -55,13 +57,3 @@ impl Tag for EndHeaderTag { type IDType = HeaderTagType; const ID: HeaderTagType = HeaderTagType::End; } - -#[cfg(test)] -mod tests { - use crate::EndHeaderTag; - - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4); - } -} diff --git a/multiboot2-header/src/header.rs b/multiboot2-header/src/header.rs index 41c02065..7739ee45 100644 --- a/multiboot2-header/src/header.rs +++ b/multiboot2-header/src/header.rs @@ -362,6 +362,8 @@ pub struct Multiboot2BasicHeader { // At minimum, the end tag. } +const _: () = assert!(size_of::() == 4 + 4 + 4 + 4); + impl Multiboot2BasicHeader { #[cfg(feature = "builder")] /// Constructor for the basic header. @@ -474,11 +476,6 @@ mod tests { buffer[20..24].copy_from_slice(&8_u32.to_le_bytes()); } - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 4 + 4 + 4 + 4); - } - #[test] fn find_header_handles_short_buffers() { let bytes = AlignedBytes::new([0; 16]); diff --git a/multiboot2-header/src/module_align.rs b/multiboot2-header/src/module_align.rs index 2055d7b7..ce0c43c6 100644 --- a/multiboot2-header/src/module_align.rs +++ b/multiboot2-header/src/module_align.rs @@ -8,6 +8,8 @@ pub struct ModuleAlignHeaderTag { header: HeaderTagHeader, } +const _: () = assert!(size_of::() == 2 + 2 + 4); + impl ModuleAlignHeaderTag { /// Constructs a new tag. #[must_use] @@ -46,13 +48,3 @@ impl Tag for ModuleAlignHeaderTag { type IDType = HeaderTagType; const ID: HeaderTagType = HeaderTagType::ModuleAlign; } - -#[cfg(test)] -mod tests { - use crate::ModuleAlignHeaderTag; - - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4); - } -} diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index 75b6946f..4d8239f9 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -39,6 +39,8 @@ pub struct RelocatableHeaderTag { preference: RelocatableHeaderTagPreferenceRaw, } +const _: () = assert!(size_of::() == 2 + 2 + 4 + 4 + 4 + 4 + 4); + impl RelocatableHeaderTag { /// Constructs a new tag. #[must_use] @@ -136,11 +138,6 @@ mod tests { use core::borrow::Borrow; use multiboot2_common::test_utils::AlignedBytes; - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4 + 4 + 4 + 4 + 4); - } - /// A tag with a placement preference unknown to the specification must /// be parsable without undefined behavior. #[test] diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index fa59a90a..1bfd34f8 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -85,6 +85,8 @@ pub struct HeaderTagHeader { // Followed by optional additional tag-specific fields. } +const _: () = assert!(size_of::() == 2 + 2 + 4); + impl HeaderTagHeader { /// Creates a new header. #[must_use] @@ -124,13 +126,3 @@ impl Header for HeaderTagHeader { self.size = total_size as u32; } } - -#[cfg(test)] -mod tests { - use crate::HeaderTagHeader; - - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4); - } -} diff --git a/multiboot2-header/src/uefi_bs.rs b/multiboot2-header/src/uefi_bs.rs index 241f62a9..29a88787 100644 --- a/multiboot2-header/src/uefi_bs.rs +++ b/multiboot2-header/src/uefi_bs.rs @@ -9,6 +9,8 @@ pub struct EfiBootServiceHeaderTag { header: HeaderTagHeader, } +const _: () = assert!(size_of::() == 2 + 2 + 4); + impl EfiBootServiceHeaderTag { /// Constructs a new tag. #[must_use] @@ -46,13 +48,3 @@ impl Tag for EfiBootServiceHeaderTag { type IDType = HeaderTagType; const ID: HeaderTagType = HeaderTagType::EfiBS; } - -#[cfg(test)] -mod tests { - use crate::EfiBootServiceHeaderTag; - - #[test] - fn test_assert_size() { - assert_eq!(size_of::(), 2 + 2 + 4); - } -} diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index 6ed797c4..5c6f743f 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -389,6 +389,8 @@ pub struct FramebufferColor { pub blue: u8, } +const _: () = assert!(size_of::() == 3); + /// Error when an unknown framebuffer type is found. #[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] #[error("Unknown framebuffer type {0}")] @@ -401,12 +403,6 @@ mod tests { use core::borrow::Borrow; use multiboot2_common::test_utils::AlignedBytes; - // Compile time test - #[test] - fn test_size() { - assert_eq!(size_of::(), 3) - } - #[test] #[cfg(feature = "builder")] fn create_new() { diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index 43db572e..1c02e7c9 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -142,6 +142,8 @@ pub struct VBEControlInfo { oem_data: [u8; 256], } +const _: () = assert!(size_of::() == 512); + impl fmt::Debug for VBEControlInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("VBEControlInfo") @@ -275,6 +277,8 @@ pub struct VBEModeInfo { reserved1: [u8; 206], } +const _: () = assert!(size_of::() == 256); + impl fmt::Debug for VBEModeInfo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("VBEModeInfo") @@ -481,13 +485,6 @@ multiboot2_common::raw_type! { mod tests { use super::*; - /// The sizes of the structs given in the VBE specification. - #[test] - fn test_layout() { - assert_eq!(size_of::(), 512); - assert_eq!(size_of::(), 256); - } - /// A mode info block with a memory model unknown to the specification /// must be readable without undefined behavior. #[test] From af150a04ce3091d505731a3f006a786eb50eeb9a Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Aug 2026 07:04:57 +0200 Subject: [PATCH 13/16] multiboot2-common: model the doc convention in raw_type! docs Raw types start with "ABI compatible representation of ..."; the high-level enums first explain what the type is and then reference the raw type they abstract. --- multiboot2-common/src/raw.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/multiboot2-common/src/raw.rs b/multiboot2-common/src/raw.rs index 9ba8e42d..6265b929 100644 --- a/multiboot2-common/src/raw.rs +++ b/multiboot2-common/src/raw.rs @@ -21,10 +21,12 @@ /// /// ``` /// multiboot2_common::raw_type! { -/// /// Binary representation of a demo type. +/// /// ABI compatible representation of a demo type. /// pub struct DemoTypeRaw(u32); /// -/// /// High-level abstraction of the possible demo types. +/// /// The type of a demo item. +/// /// +/// /// This is a higher level abstraction for [`DemoTypeRaw`]. /// pub enum DemoType { /// /// The first defined type. /// Foo = 0, @@ -213,10 +215,12 @@ mod tests { use std::collections::{BTreeSet, HashSet}; crate::raw_type! { - /// Binary representation of a test type. + /// ABI compatible representation of a test type. pub struct TestRaw(u16); - /// High-level abstraction of a test type. + /// The type of a test item. + /// + /// This is a higher level abstraction for [`TestRaw`]. pub enum TestType { /// The first defined value. Foo = 0, From c00672f019f7c0ae22e8f72fc249086c598c10cd Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Aug 2026 07:05:24 +0200 Subject: [PATCH 14/16] multiboot2: streamline docs of raw types and their high-level enums Raw types consistently start with "ABI compatible representation of ..."; the high-level enums first explain what the type is according to the spec and then reference the raw type they abstract. --- multiboot2/src/framebuffer.rs | 14 +++++++++----- multiboot2/src/memory_map.rs | 17 ++++++++++------- multiboot2/src/tag_type.rs | 23 ++++++++++++----------- multiboot2/src/vbe_info.rs | 13 ++++++++----- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/multiboot2/src/framebuffer.rs b/multiboot2/src/framebuffer.rs index 5c6f743f..dc86a491 100644 --- a/multiboot2/src/framebuffer.rs +++ b/multiboot2/src/framebuffer.rs @@ -266,14 +266,18 @@ impl PartialEq for FramebufferTag { } multiboot2_common::raw_type! { - /// ABI-compatible framebuffer type. + /// ABI compatible representation of the framebuffer type of the + /// framebuffer tag. + /// + /// This type matches the binary representation (`u8`). pub struct FramebufferTypeRaw(u8); - /// Higher level abstraction for [`FramebufferTypeRaw`] that assigns each - /// possible value to a specific semantic according to the specification. + /// The kind of framebuffer described by the framebuffer tag according to + /// the Multiboot2 spec. /// - /// Unlike [`FramebufferType`], this only describes the kind of - /// framebuffer and not its payload. + /// This is a higher level abstraction for [`FramebufferTypeRaw`]. Unlike + /// [`FramebufferType`], it only describes the kind of framebuffer and + /// not its payload. #[allow(clippy::upper_case_acronyms)] pub enum FramebufferKind { /// Indexed color. diff --git a/multiboot2/src/memory_map.rs b/multiboot2/src/memory_map.rs index 2831001a..6b68d86d 100644 --- a/multiboot2/src/memory_map.rs +++ b/multiboot2/src/memory_map.rs @@ -150,16 +150,19 @@ impl Debug for MemoryArea { } multiboot2_common::raw_type! { - /// ABI-friendly version of [`MemoryAreaType`]. + /// ABI compatible representation of the type of a memory area. + /// + /// This type matches the binary representation (`u32`). pub struct MemoryAreaTypeRaw(u32); - /// Abstraction over defined memory types for the memory map as well as - /// custom ones. Types 1 to 5 are defined in the Multiboot2 spec and - /// correspond to the entry types of e820 memory maps; other values are - /// mapped to [`MemoryAreaType::Custom`]. + /// The type of a memory area of the memory map. + /// + /// Types 1 to 5 are defined in the Multiboot2 spec and correspond to the + /// entry types of e820 memory maps; other values are mapped to + /// [`MemoryAreaType::Custom`]. /// - /// This is not binary compatible with the Multiboot2 spec. Please use - /// [`MemoryAreaTypeRaw`] instead. + /// This is a higher level abstraction for [`MemoryAreaTypeRaw`] and not + /// binary compatible with the Multiboot2 spec. pub enum MemoryAreaType { /// Available memory free to be used by the OS. Available = 1, diff --git a/multiboot2/src/tag_type.rs b/multiboot2/src/tag_type.rs index f0edf853..b2247fa1 100644 --- a/multiboot2/src/tag_type.rs +++ b/multiboot2/src/tag_type.rs @@ -3,23 +3,24 @@ //! The relevant exports of this module are [`TagTypeRaw`] and [`TagType`]. multiboot2_common::raw_type! { - /// Serialized form of [`TagType`] that matches the binary representation - /// (`u32`). + /// ABI compatible representation of the type of a boot information tag. /// - /// The abstraction corresponds to the `typ`/`type` field of a Multiboot2 - /// [`TagHeader`]. This type can easily be created from or converted to - /// [`TagType`]. + /// This type matches the binary representation (`u32`) and corresponds + /// to the `typ`/`type` field of a Multiboot2 [`TagHeader`]. It can + /// easily be created from or converted to [`TagType`]. /// /// [`TagHeader`]: crate::TagHeader pub struct TagTypeRaw(u32); - /// Higher level abstraction for [`TagTypeRaw`] that assigns each possible - /// value to a specific semantic according to the specification. + /// The type of a boot information tag. /// - /// Additionally, it allows to use the [`TagType::Custom`] variant for - /// custom tag types `> 21`. The Multiboot2 spec doesn't explicitly allow - /// or disallow them; bootloader and OS developers are free to use custom - /// tags. It is **not binary compatible** with [`TagTypeRaw`]. + /// This assigns each possible value a specific semantic according to the + /// Multiboot2 spec. Custom tag types `> 21` are mapped to + /// [`TagType::Custom`]; the spec doesn't explicitly allow or disallow + /// them. + /// + /// This is a higher level abstraction for [`TagTypeRaw`] and **not + /// binary compatible** with it. pub enum TagType { /// Tag `0`: Marks the end of the tags. End = 0, diff --git a/multiboot2/src/vbe_info.rs b/multiboot2/src/vbe_info.rs index 1c02e7c9..678f1e6e 100644 --- a/multiboot2/src/vbe_info.rs +++ b/multiboot2/src/vbe_info.rs @@ -448,16 +448,19 @@ bitflags! { } multiboot2_common::raw_type! { - /// ABI-compatible form of [`VBEMemoryModel`] that matches the binary - /// representation (`u8`). + /// ABI compatible representation of the VBE memory model. + /// + /// This type matches the binary representation (`u8`). #[derive(Default)] pub struct VBEMemoryModelRaw(u8); - /// The MemoryModel field specifies the general type of memory organization used in modes. + /// The general type of memory organization used in a VBE mode. /// - /// Values `0x08..=0x0F` are reserved and values `0x10..=0xFF` are defined - /// by the OEM according to the VBE specification. Both are mapped to + /// Reserved values (`0x08..=0x0F`) and OEM-defined values + /// (`0x10..=0xFF`) of the VBE spec are mapped to /// [`VBEMemoryModel::Custom`]. + /// + /// This is a higher level abstraction for [`VBEMemoryModelRaw`]. #[derive(Default)] #[allow(clippy::upper_case_acronyms)] pub enum VBEMemoryModel { From 20f434079c04a69d250a87d418bf3a0f941b76ac Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Aug 2026 07:05:51 +0200 Subject: [PATCH 15/16] multiboot2-header: streamline docs of raw types and their high-level enums Raw types consistently start with "ABI compatible representation of ..."; the high-level enums first explain what the type is according to the spec and then reference the raw type they abstract. --- multiboot2-header/src/console.rs | 13 +++++---- multiboot2-header/src/relocatable.rs | 11 +++++-- multiboot2-header/src/tags.rs | 43 +++++++++++++++++----------- 3 files changed, 41 insertions(+), 26 deletions(-) diff --git a/multiboot2-header/src/console.rs b/multiboot2-header/src/console.rs index 9da53ec9..6b773978 100644 --- a/multiboot2-header/src/console.rs +++ b/multiboot2-header/src/console.rs @@ -2,14 +2,15 @@ use crate::{HeaderTagFlag, HeaderTagHeader, HeaderTagType}; use multiboot2_common::{MaybeDynSized, Tag}; multiboot2_common::raw_type! { - /// Serialized form of [`ConsoleHeaderTagFlags`] that matches the binary - /// representation (`u32`). + /// ABI compatible representation of the console flags of the + /// [`ConsoleHeaderTag`]. + /// + /// This type matches the binary representation (`u32`). pub struct ConsoleHeaderTagFlagsRaw(u32); - /// Possible flags for [`ConsoleHeaderTag`]. + /// The console flags of the [`ConsoleHeaderTag`]. /// - /// The values are taken from the example C code at the bottom of the - /// Multiboot2 specification. + /// This is a higher level abstraction for [`ConsoleHeaderTagFlagsRaw`]. pub enum ConsoleHeaderTagFlags { /// At least one of the consoles supported by the bootloader must be /// present and information about it must be available in the boot @@ -84,7 +85,7 @@ mod tests { use core::borrow::Borrow; use multiboot2_common::test_utils::AlignedBytes; - /// The console flag values must match the example C code of the + /// The console flag values must match the values mandated by the /// specification. #[test] fn console_flags_match_spec_values() { diff --git a/multiboot2-header/src/relocatable.rs b/multiboot2-header/src/relocatable.rs index 4d8239f9..9c7b7eb8 100644 --- a/multiboot2-header/src/relocatable.rs +++ b/multiboot2-header/src/relocatable.rs @@ -4,11 +4,16 @@ use core::fmt::{Debug, Formatter}; use multiboot2_common::{MaybeDynSized, Tag}; multiboot2_common::raw_type! { - /// Serialized form of [`RelocatableHeaderTagPreference`] that matches the - /// binary representation (`u32`). + /// ABI compatible representation of the placement preference of the + /// relocatable header tag. + /// + /// This type matches the binary representation (`u32`). pub struct RelocatableHeaderTagPreferenceRaw(u32); - /// Specifies the bootloader's preferred placement for a relocatable image. + /// The bootloader's preferred placement for a relocatable image. + /// + /// This is a higher level abstraction for + /// [`RelocatableHeaderTagPreferenceRaw`]. pub enum RelocatableHeaderTagPreference { /// Let the bootloader choose the image location. None = 0, diff --git a/multiboot2-header/src/tags.rs b/multiboot2-header/src/tags.rs index 1bfd34f8..87fdab8c 100644 --- a/multiboot2-header/src/tags.rs +++ b/multiboot2-header/src/tags.rs @@ -1,16 +1,18 @@ -//! Definition for all types of "Multiboot2 header tags". The values are taken from the example C -//! code at the end of the official Multiboot2 spec. These tags follow in memory right after -//! [`crate::Multiboot2BasicHeader`]. +//! Definition for all types of "Multiboot2 header tags". These tags follow in +//! memory right after [`crate::Multiboot2BasicHeader`]. use multiboot2_common::Header; multiboot2_common::raw_type! { - /// Serialized form of [`HeaderTagISA`] that matches the binary - /// representation (`u32`). This value stands in the `arch` property of - /// [`crate::Multiboot2BasicHeader`]. + /// ABI compatible representation of the ISA/ARCH of a Multiboot2 header. + /// + /// This type matches the binary representation (`u32`) and stands in the + /// `arch` property of [`crate::Multiboot2BasicHeader`]. pub struct HeaderTagISARaw(u32); - /// ISA/ARCH in Multiboot2 header. + /// The ISA/ARCH a Multiboot2 image targets. + /// + /// This is a higher level abstraction for [`HeaderTagISARaw`]. pub enum HeaderTagISA { /// Spec: "means 32-bit (protected) mode of i386". /// Caution: This is confusing. If you use the EFI64-tag @@ -23,15 +25,15 @@ multiboot2_common::raw_type! { } multiboot2_common::raw_type! { - /// Serialized form of [`HeaderTagType`] that matches the binary - /// representation (`u16`). This value stands in the `typ` property of - /// [`HeaderTagHeader`]. + /// ABI compatible representation of the type of a Multiboot2 header tag. + /// + /// This type matches the binary representation (`u16`) and stands in the + /// `typ` property of [`HeaderTagHeader`]. pub struct HeaderTagTypeRaw(u16); - /// Possible types for header tags of a Multiboot2 header. + /// The type of a Multiboot2 header tag. /// - /// The names and values are taken from the example C code at the bottom of - /// the Multiboot2 specification. + /// This is a higher level abstraction for [`HeaderTagTypeRaw`]. pub enum HeaderTagType { /// Type for [`crate::EndHeaderTag`]. End = 0, @@ -59,12 +61,19 @@ multiboot2_common::raw_type! { } multiboot2_common::raw_type! { - /// Serialized form of [`HeaderTagFlag`] that matches the binary - /// representation (`u16`). This value stands in the `flags` property of - /// [`HeaderTagHeader`]. + /// ABI compatible representation of the flags of a Multiboot2 header + /// tag. + /// + /// This type matches the binary representation (`u16`) and stands in the + /// `flags` property of [`HeaderTagHeader`]. pub struct HeaderTagFlagRaw(u16); - /// Flags for Multiboot2 header tags. + /// Flags of a Multiboot2 header tag. + /// + /// These flags tell whether a tag is required or optional for the + /// bootloader. + /// + /// This is a higher level abstraction for [`HeaderTagFlagRaw`]. pub enum HeaderTagFlag { /// The bootloader must provide this tag. If this is not possible, the /// bootloader will fail to load the kernel. From 0b197e33d9a314e2389bc7254e1b49de36e2f010 Mon Sep 17 00:00:00 2001 From: Philipp Schuster Date: Wed, 26 Aug 2026 07:46:18 +0200 Subject: [PATCH 16/16] multiboot2: fix MSRV clippy --- multiboot2/src/boot_loader_name.rs | 2 +- multiboot2/src/command_line.rs | 2 +- multiboot2/src/module.rs | 2 +- multiboot2/src/smbios.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/multiboot2/src/boot_loader_name.rs b/multiboot2/src/boot_loader_name.rs index e7967f3c..0cc61f08 100644 --- a/multiboot2/src/boot_loader_name.rs +++ b/multiboot2/src/boot_loader_name.rs @@ -99,7 +99,7 @@ mod tests { use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] - fn get_bytes() -> AlignedBytes<16> { + const fn get_bytes() -> AlignedBytes<16> { AlignedBytes::new([ TagType::BootLoaderName.val() as u8, 0, 0, 0, 14, 0, 0, 0, diff --git a/multiboot2/src/command_line.rs b/multiboot2/src/command_line.rs index 3678e596..e54d4f0c 100644 --- a/multiboot2/src/command_line.rs +++ b/multiboot2/src/command_line.rs @@ -93,7 +93,7 @@ mod tests { use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] - fn get_bytes() -> AlignedBytes<16> { + const fn get_bytes() -> AlignedBytes<16> { AlignedBytes::new([ TagType::Cmdline.val() as u8, 0, 0, 0, 14, 0, 0, 0, diff --git a/multiboot2/src/module.rs b/multiboot2/src/module.rs index 5052e246..8c57f498 100644 --- a/multiboot2/src/module.rs +++ b/multiboot2/src/module.rs @@ -139,7 +139,7 @@ mod tests { use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] - fn get_bytes() -> AlignedBytes<24> { + const fn get_bytes() -> AlignedBytes<24> { AlignedBytes::new([ TagType::Module.val() as u8, 0, 0, 0, 22, 0, 0, 0, diff --git a/multiboot2/src/smbios.rs b/multiboot2/src/smbios.rs index ee158608..ec85f63c 100644 --- a/multiboot2/src/smbios.rs +++ b/multiboot2/src/smbios.rs @@ -83,7 +83,7 @@ mod tests { use multiboot2_common::test_utils::AlignedBytes; #[rustfmt::skip] - fn get_bytes() -> AlignedBytes<32> { + const fn get_bytes() -> AlignedBytes<32> { AlignedBytes::new([ TagType::Smbios.val() as u8, 0, 0, 0, 25, 0, 0, 0,