Skip to content

Various UB Fixes - #317

Open
phip1611 wants to merge 11 commits into
mainfrom
ub
Open

Various UB Fixes#317
phip1611 wants to merge 11 commits into
mainfrom
ub

Conversation

@phip1611

@phip1611 phip1611 commented Sep 2, 2026

Copy link
Copy Markdown
Member

No description provided.

phip1611 and others added 11 commits September 2, 2026 10:53
All other dst_len implementations assert that the reported tag size
covers the base size before subtracting; NetworkTag was the only one
missing this guard. An undersized header now panics consistently
instead of underflowing, which would report a huge DST length in
release mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
The string table is not part of the tag: the tag only stores the
physical address (sh_addr) and size of the section holding it, both
taken verbatim from the boot information. string_table() dereferenced
that wire-provided address from safe code, so corrupted or malicious
boot information made safe code exhibit undefined behavior - a crafted
tag segfaults natively and Miri reports a dangling reference.

Make the function unsafe and document the caller's obligations.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
… underflow

All other dst_len implementations assert that the reported tag size
covers the base size before subtracting; this one was missing the
guard. An undersized header now panics consistently instead of
underflowing, which would report a huge DST length in release mode.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
Stack-constructed sized tags contain uninitialized implicit trailing
padding, which as_bytes() exposed and build() copied into the boot
information (undefined behavior, caught by Miri). The new push_tag
helper only reads the bytes up to the size reported in the tag header
and appends explicit zeroed padding slices up to the next 8-byte
boundary, as mandated by the Multiboot2 spec. The serialized boot
information is byte-wise identical, except that inter-tag padding is
now guaranteed to be zeroed instead of copied from uninitialized
memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DCAJEf5o6ADp5Ayq2915T
…build

Stack-constructed sized tags contain uninitialized implicit trailing
padding, which as_bytes() exposed and build() copied into the built
header (undefined behavior, caught by Miri). The new push_tag helper
only reads the bytes up to the size reported in the tag header and
appends explicit zeroed padding slices up to the next 8-byte boundary,
as mandated by the Multiboot2 spec. The serialized header is byte-wise
identical, except that inter-tag padding is now guaranteed to be
zeroed instead of copied from uninitialized memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DCAJEf5o6ADp5Ayq2915T
…ytes

Regression test for the uninitialized-padding fixes: reading every byte
of the built MBI is defined behavior only if all serialized tags are
fully initialized. Miri catches any regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
Regression test for the uninitialized-padding fixes: reading every byte
of the built header is defined behavior only if all serialized tags are
fully initialized. Miri catches any regression.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
MaybeDynSized::as_bytes previously covered size_of_val(self) bytes and
thus included the implicit trailing padding that the Rust memory layout
adds beyond the structure size reported in the header. For
stack-constructed sized tags, that padding is uninitialized memory, so
reading the returned slice was undefined behavior (caught by Miri).
Zeroing the padding in the constructors is no fix: the language resets
padding bytes to uninitialized on every typed copy, including returning
the value from the constructor.

Instead, as_bytes now returns a plain byte slice covering exactly the
reported structure size, clamped to the allocation, so uninitialized
padding is never exposed. The BytesRef return type had to go, as the
reported size is not necessarily a multiple of the tag alignment;
BytesRef remains the input type of the parsing path. The builders keep
tags 8-byte aligned via their explicit zeroed padding slices, and heap
allocations of new_boxed remain fully zero-initialized.

As a side effect, payload() and therefore clone_dyn() now operate on
the exact payload: cloning a DST whose size is no multiple of the
alignment previously grew the reported size to the padded allocation
size; now the size round-trips exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DCAJEf5o6ADp5Ayq2915T
Both traits carry soundness-critical contracts that safe code could
violate: ref_from_bytes/ref_from_slice materialize &H from raw bytes
(a Header type with a niche, e.g. a NonZeroU32 field, leads to an
invalid value), and cast/new_boxed derive reference extents from
BASE_SIZE/dst_len (a wrong dst_len creates a reference beyond the
allocation). Miri confirms both as undefined behavior with purely safe
downstream impls.

Mark both traits unsafe and document the implementor's obligations.
No runtime test can cover this; the compiler now enforces that
implementors opt in with unsafe impl.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018T6JCw8iJLXmDoPiBSF2HG
One representative test for all stack-constructed sized tags with
implicit trailing padding: the constructor plus as_bytes() must expose
exactly the reported tag size and only initialized bytes. Miri verifies
the absence of undefined behavior. ApmTag is the tag with the most
fields and the largest gap between spec size (28) and Rust type size
(32).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016DCAJEf5o6ADp5Ayq2915T
Comment thread multiboot2/src/network.rs

@phip1611 phip1611 left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's remove "Claude-Session" from every commit

Comment thread multiboot2/src/elf_sections.rs
Comment thread multiboot2/src/elf_sections.rs
Comment thread multiboot2/src/elf_sections.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant