Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion integration-test/bins/multiboot2_payload/src/verify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ pub(self) fn print_elf_info(mbi: &BootInformation) -> anyhow::Result<()> {
let string_table = mbi
.elf_sections_tag()
.ok_or("Should have elf sections")
.map(|tag| tag.string_table())
// SAFETY: The bootloader loaded the ELF sections at their reported
// addresses in identity-mapped memory.
.map(|tag| unsafe { tag.string_table() })
.map_err(anyhow::Error::msg)?
.ok_or("String table section should be present")
.map_err(anyhow::Error::msg)?;
Expand Down
3 changes: 3 additions & 0 deletions multiboot2-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
- 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.
- **Breaking:** `Header` and `MaybeDynSized` are now `unsafe` traits, as this
crate creates references from raw memory based on their implementations.
The safety contracts implementors must uphold are now documented.

## v0.5.0 (2026-08-24)

Expand Down
16 changes: 13 additions & 3 deletions multiboot2-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,13 @@ pub const ALIGNMENT: usize = 8;
///
/// The alignment of implementors **must** be compatible with the requirements
/// for the corresponding structure, which typically is [`ALIGNMENT`].
pub trait Header: Clone + Sized + PartialEq + Eq + Debug {
///
/// # Safety
///
/// Implementors must be `#[repr(C)]`, have no padding bytes or interior
/// mutability, allow every bit pattern, and have an alignment of at most
/// [`ALIGNMENT`]. Headers are referenced from raw memory and copied byte-wise.
pub unsafe trait Header: Clone + Sized + PartialEq + Eq + Debug {
/// Returns the total size of the structure in bytes, including the fixed
/// header and any dynamic payload.
#[must_use]
Expand Down Expand Up @@ -588,7 +594,9 @@ mod tests {
b: u32,
}

impl MaybeDynSized for CustomSizedTag {
// SAFETY: The tag is repr(C) with the header as first field, any
// bit pattern is valid, and `BASE_SIZE` matches the ABI.
unsafe impl MaybeDynSized for CustomSizedTag {
type Header = DummyTestHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down Expand Up @@ -661,7 +669,9 @@ mod tests {
b: u32,
}

impl MaybeDynSized for CustomSizedTag {
// SAFETY: The tag is repr(C) with the header as first field, any
// bit pattern is valid, and `BASE_SIZE` matches the ABI.
unsafe impl MaybeDynSized for CustomSizedTag {
type Header = DummyTestHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
23 changes: 15 additions & 8 deletions multiboot2-common/src/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,21 @@ use ptr_meta::Pointee;
/// implementation. Only dynamically sized types need to implement
/// [`MaybeDynSized::dst_len`].
///
/// # ABI
/// Implementors **must** use `#[repr(C)]`. As there might be padding necessary
/// for the proper Rust layout, `size_of_val(&self)` might report additional
/// padding bytes that are not reflected by the actual payload. These additional
/// padding bytes however will be reflected in corresponding [`BytesRef`]
/// instances.
/// # Safety
///
/// Implementors must be `#[repr(C)]`, start with `Self::Header`, have an
/// alignment of at most [`ALIGNMENT`], and allow every bit pattern.
///
/// [`MaybeDynSized::BASE_SIZE`], [`MaybeDynSized::dst_len`], and
/// [`Header::total_size`] must correctly describe the initialized,
/// contiguous memory backing the value. Incorrect sizes or implicit padding
/// within the reported range can cause out-of-bounds references. Trailing
/// padding beyond that range is fine.
///
/// [`ID`]: Tag::ID
/// [`ALIGNMENT`]: crate::ALIGNMENT
/// [`DynSizedStructure`]: crate::DynSizedStructure
pub trait MaybeDynSized: Pointee {
pub unsafe trait MaybeDynSized: Pointee {
/// The associated [`Header`] of this tag.
type Header: Header;

Expand Down Expand Up @@ -105,7 +110,9 @@ pub trait Tag: MaybeDynSized {

// This implementation is not needed for parsing but for creation, when
// downstream types just wrap this type.
impl<H: Header> MaybeDynSized for DynSizedStructure<H> {
// SAFETY: `DynSizedStructure` is repr(C) with the header as first field,
// any bit pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl<H: Header> MaybeDynSized for DynSizedStructure<H> {
type Header = H;

const BASE_SIZE: usize = size_of::<H>();
Expand Down
8 changes: 6 additions & 2 deletions multiboot2-common/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ impl DummyTestHeader {
}
}

impl Header for DummyTestHeader {
// SAFETY: The header is a padding-free repr(C) struct of raw integers, and
// any bit pattern is valid for it.
unsafe impl Header for DummyTestHeader {
fn total_size(&self) -> usize {
self.size as usize
}
Expand Down Expand Up @@ -101,7 +103,9 @@ impl DummyDstTag {
}
}

impl MaybeDynSized for DummyDstTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for DummyDstTag {
type Header = DummyTestHeader;

const BASE_SIZE: usize = size_of::<DummyTestHeader>();
Expand Down
4 changes: 4 additions & 0 deletions multiboot2-header/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
`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.
- Fixed a possible size underflow in `InformationRequestHeaderTag` parsing
when the tag reports a size smaller than the tag header.
- **Breaking:** The re-exported `MaybeDynSized` trait is now an `unsafe`
trait; implementations for custom tag types must now use `unsafe impl`.

## v0.10.0 (2026-08-24)

Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ impl AddressHeaderTag {
}
}

impl MaybeDynSized for AddressHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for AddressHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ impl ConsoleHeaderTag {
}
}

impl MaybeDynSized for ConsoleHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for ConsoleHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>() + size_of::<u32>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/end.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,9 @@ impl EndHeaderTag {
}
}

impl MaybeDynSized for EndHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for EndHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/entry_address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ impl Debug for EntryAddressHeaderTag {
}
}

impl MaybeDynSized for EntryAddressHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for EntryAddressHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>() + size_of::<u32>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/entry_efi_32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ impl Debug for EntryEfi32HeaderTag {
}
}

impl MaybeDynSized for EntryEfi32HeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for EntryEfi32HeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>() + size_of::<u32>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/entry_efi_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ impl Debug for EntryEfi64HeaderTag {
}
}

impl MaybeDynSized for EntryEfi64HeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for EntryEfi64HeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>() + size_of::<u32>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/framebuffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ impl FramebufferHeaderTag {
}
}

impl MaybeDynSized for FramebufferHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for FramebufferHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>() + 3 * size_of::<u32>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,9 @@ impl Multiboot2BasicHeader {
}
}

impl DynSizedHeader for Multiboot2BasicHeader {
// SAFETY: The header is a padding-free repr(C) struct of raw integers, and
// any bit pattern is valid for it.
unsafe impl DynSizedHeader for Multiboot2BasicHeader {
fn total_size(&self) -> usize {
self.length as usize
}
Expand Down
16 changes: 15 additions & 1 deletion multiboot2-header/src/information_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,15 @@ impl Debug for InformationRequestHeaderTag {
}
}

impl MaybeDynSized for InformationRequestHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for InformationRequestHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<HeaderTagHeader>();

fn dst_len(header: &Self::Header) -> Self::Metadata {
assert!(header.size() as usize >= Self::BASE_SIZE);
let dst_size = header.size() as usize - Self::BASE_SIZE;
assert_eq!(dst_size % size_of::<MbiTagTypeRaw>(), 0);
dst_size / size_of::<MbiTagTypeRaw>()
Expand All @@ -97,6 +100,17 @@ impl Tag for InformationRequestHeaderTag {
mod tests {
use super::*;

#[test]
#[should_panic]
fn dst_len_rejects_undersized_header() {
let header = HeaderTagHeader::new(
HeaderTagType::InformationRequest,
HeaderTagFlag::Optional,
4,
);
let _ = <InformationRequestHeaderTag as MaybeDynSized>::dst_len(&header);
}

#[test]
fn creation() {
let requests = [
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/module_align.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ impl ModuleAlignHeaderTag {
}
}

impl MaybeDynSized for ModuleAlignHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for ModuleAlignHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/relocatable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ impl Debug for RelocatableHeaderTag {
}
}

impl MaybeDynSized for RelocatableHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for RelocatableHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/tags.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ impl HeaderTagHeader {
}
}

impl Header for HeaderTagHeader {
// SAFETY: The header is a padding-free repr(C) struct of raw integers, and
// any bit pattern is valid for it.
unsafe impl Header for HeaderTagHeader {
fn total_size(&self) -> usize {
self.size as usize
}
Expand Down
4 changes: 3 additions & 1 deletion multiboot2-header/src/uefi_bs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ impl EfiBootServiceHeaderTag {
}
}

impl MaybeDynSized for EfiBootServiceHeaderTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for EfiBootServiceHeaderTag {
type Header = HeaderTagHeader;

const BASE_SIZE: usize = size_of::<Self>();
Expand Down
7 changes: 7 additions & 0 deletions multiboot2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
- **Breaking:** `EFIMemoryDesc` (re-exported from `uefi-raw` crate) has a new
padding field which is required for correct type layout on some non-UEFI
32-bit targets.
- Fixed a possible size underflow in `NetworkTag` parsing when the tag reports
a size smaller than the tag header.
- **Breaking:** `ElfSectionsTag::string_table()` is now an `unsafe fn`. The
string table is not part of the tag itself; the caller must ensure that the
referenced section memory is still loaded and valid.
- **Breaking:** The re-exported `MaybeDynSized` trait is now an `unsafe`
trait; implementations for custom tag types must now use `unsafe impl`.

## v0.26.0 / v0.26.1 (2026-08-24)

Expand Down
4 changes: 3 additions & 1 deletion multiboot2/src/apm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,9 @@ impl ApmTag {
}
}

impl MaybeDynSized for ApmTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for ApmTag {
type Header = TagHeader;

// Spec size (28), excluding the trailing padding that `size_of::<Self>()`
Expand Down
8 changes: 6 additions & 2 deletions multiboot2/src/boot_information.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ impl BootInformationHeader {
}
}

impl Header for BootInformationHeader {
// SAFETY: The header is a padding-free repr(C) struct of raw integers, and
// any bit pattern is valid for it.
unsafe impl Header for BootInformationHeader {
fn total_size(&self) -> usize {
self.total_size as usize
}
Expand Down Expand Up @@ -391,7 +393,9 @@ impl<'a> BootInformation<'a> {
/// }
///
/// // Give the library hints how big this tag is.
/// impl MaybeDynSized for CustomTag {
/// // SAFETY: The tag is repr(C) with the header as first field, any bit
/// // pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
/// unsafe impl MaybeDynSized for CustomTag {
/// type Header = TagHeader;
/// const BASE_SIZE: usize = size_of::<TagHeader>() + size_of::<u32>();
///
Expand Down
4 changes: 3 additions & 1 deletion multiboot2/src/boot_loader_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ impl Debug for BootLoaderNameTag {
}
}

impl MaybeDynSized for BootLoaderNameTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for BootLoaderNameTag {
type Header = TagHeader;

const BASE_SIZE: usize = size_of::<TagHeader>();
Expand Down
4 changes: 3 additions & 1 deletion multiboot2/src/bootdev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ impl BootdevTag {
}
}

impl MaybeDynSized for BootdevTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for BootdevTag {
type Header = TagHeader;

// Spec size (20), excluding the trailing padding that `size_of::<Self>()`
Expand Down
4 changes: 3 additions & 1 deletion multiboot2/src/command_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ impl Debug for CommandLineTag {
}
}

impl MaybeDynSized for CommandLineTag {
// SAFETY: The tag is repr(C) with the header as first field, any bit
// pattern is valid, and `BASE_SIZE`/`dst_len` match the ABI.
unsafe impl MaybeDynSized for CommandLineTag {
type Header = TagHeader;

const BASE_SIZE: usize = size_of::<TagHeader>();
Expand Down
Loading