Skip to content
Open
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,10 @@ Please see:
- [multiboot2/CHANGELOG.md](multiboot2/CHANGELOG.md)
- [multiboot2-header/CHANGELOG.md](multiboot2-header/CHANGELOG.md)
- [multiboot2-common/CHANGELOG.md](multiboot2-common/CHANGELOG.md)

Integration tests:

- Allowed the shared integration utility crate to compile under the host test
harness by using the harness allocator and panic implementation for tests.
- Fixed chainloader BSS initialization to zero the full segment instead of
repeatedly writing the first byte.
12 changes: 3 additions & 9 deletions integration-test/bins/multiboot2_chainloader/src/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ pub fn load_module(mut modules: multiboot::information::ModuleIter) -> ! {

// Check if a header is present.
{
let hdr = multiboot2_header::Multiboot2Header::find_header(elf_bytes)
.unwrap()
let (hdr, _) = multiboot2_header::Multiboot2Header::find_header(elf_bytes)
.expect("Should have Multiboot2 header");
let hdr =
unsafe { multiboot2_header::Multiboot2Header::load(hdr.0.as_ptr().cast()) }.unwrap();
log::info!("Multiboot2 header:\n{hdr:#?}");
}

Expand Down Expand Up @@ -91,9 +88,6 @@ fn map_memory(ph: ProgramHeaderEntry) {
let dest_ptr = unsafe { dest_ptr.add(ph.filesz() as usize) };

// Zero .bss memory
for _ in 0..(ph.memsz() - ph.filesz()) {
unsafe {
core::ptr::write(dest_ptr, 0);
}
}
assert!(ph.memsz() >= ph.filesz());
unsafe { core::ptr::write_bytes(dest_ptr, 0, (ph.memsz() - ph.filesz()) as usize) };
}
9 changes: 9 additions & 0 deletions integration-test/bins/util/src/allocator.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
#[cfg(not(test))]
use good_memory_allocator::SpinLockedAllocator;

#[cfg(not(test))]
#[repr(align(0x4000))]
struct Align16K<T>(T);

/// 16 KiB naturally aligned backing storage for heap.
#[cfg(not(test))]
static mut HEAP: Align16K<[u8; 0x4000]> = Align16K([0; 0x4000]);

#[cfg(not(test))]
#[global_allocator]
static ALLOCATOR: SpinLockedAllocator = SpinLockedAllocator::empty();

/// Initializes the allocator. Call only once.
#[cfg(not(test))]
pub fn init() {
#[expect(static_mut_refs)]
unsafe {
ALLOCATOR.init(HEAP.0.as_ptr().cast::<usize>() as _, HEAP.0.len());
}
}

/// Initializes the allocator. Host tests use the test harness allocator.
#[cfg(test)]
pub fn init() {}
3 changes: 3 additions & 0 deletions integration-test/bins/util/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#[macro_use]
extern crate alloc;

#[cfg(not(test))]
use core::panic::PanicInfo;
#[cfg(not(test))]
use log::error;
use qemu_exit::QEMUExit;

Expand All @@ -26,6 +28,7 @@ pub fn init_environment() {
log::info!("Allocator initialized! {:?}", vec![1, 2, 3]);
}

#[cfg(not(test))]
#[panic_handler]
fn panic_handler(info: &PanicInfo) -> ! {
error!("PANIC! {}", info);
Expand Down
3 changes: 3 additions & 0 deletions multiboot2-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Added size details to memory validation errors.
- Fixed validation for dynamically sized structures whose reported total size
exceeds the available buffer.
- Small code improvements


Expand Down
9 changes: 4 additions & 5 deletions multiboot2-common/src/boxed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
use crate::{ALIGNMENT, Header, MaybeDynSized, increase_to_alignment};
use alloc::boxed::Box;
use core::alloc::Layout;
use core::mem;
use core::ops::Deref;
use core::ptr;

Expand Down Expand Up @@ -31,7 +30,7 @@ pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
.map(|b| b.len())
.sum::<usize>();

let tag_size = mem::size_of::<T::Header>() + additional_size;
let tag_size = size_of::<T::Header>() + additional_size;
header.set_size(tag_size);

// Allocation size is multiple of alignment.
Expand All @@ -43,7 +42,7 @@ pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(

// write header
{
let len = mem::size_of::<T::Header>();
let len = size_of::<T::Header>();
let ptr = core::ptr::addr_of!(header);
unsafe {
ptr::copy_nonoverlapping(ptr.cast::<u8>(), heap_ptr, len);
Expand All @@ -52,7 +51,7 @@ pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(

// write body
{
let mut write_offset = mem::size_of::<T::Header>();
let mut write_offset = size_of::<T::Header>();
for &bytes in additional_bytes_slices {
let len = bytes.len();
let src = bytes.as_ptr();
Expand All @@ -71,7 +70,7 @@ pub fn new_boxed<T: MaybeDynSized<Metadata = usize> + ?Sized>(
// If this panic triggers, there is a fundamental flaw in my logic. This is
// not the fault of an API user.
assert_eq!(
mem::size_of_val(reference.deref()),
size_of_val(reference.deref()),
alloc_size,
"Allocation should match Rusts expectation"
);
Expand Down
3 changes: 1 addition & 2 deletions multiboot2-common/src/bytes_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

use crate::{ALIGNMENT, Header, MemoryError};
use core::marker::PhantomData;
use core::mem;
use core::ops::Deref;

/// Wraps a byte slice representing a Multiboot2 structure including an optional
Expand All @@ -25,7 +24,7 @@ impl<'a, H: Header> TryFrom<&'a [u8]> for BytesRef<'a, H> {
type Error = MemoryError;

fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
if bytes.len() < mem::size_of::<H>() {
if bytes.len() < size_of::<H>() {
return Err(MemoryError::ShorterThanHeader);
}
// Doesn't work as expected: if align_of_val(&value[0]) < ALIGNMENT {
Expand Down
3 changes: 1 addition & 2 deletions multiboot2-common/src/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use crate::{ALIGNMENT, DynSizedStructure, Header, increase_to_alignment};
use core::marker::PhantomData;
use core::mem;

/// Iterates over the tags (modelled by [`DynSizedStructure`]) of the underlying
/// byte slice. Each tag is expected to have the same common [`Header`] with
Expand Down Expand Up @@ -64,7 +63,7 @@ impl<'a, H: Header + 'a> Iterator for TagIter<'a, H> {
// See <https://doc.rust-lang.org/reference/type-layout.html>.
let slice = {
let from = self.next_tag_offset;
let len = mem::size_of::<H>() + tag_hdr.payload_len();
let len = size_of::<H>() + tag_hdr.payload_len();
let to = from + len;

// The size of (the allocation for) a value is always a multiple of
Expand Down
53 changes: 40 additions & 13 deletions multiboot2-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,6 @@ pub use iter::TagIter;
pub use tag::{MaybeDynSized, Tag};

use core::fmt::Debug;
use core::mem;
use core::ptr;
use core::ptr::NonNull;
use core::slice;
Expand Down Expand Up @@ -284,7 +283,7 @@ pub trait Header: Clone + Sized + PartialEq + Eq + Debug {
/// plus [`Header::payload_len`].
#[must_use]
fn total_size(&self) -> usize {
mem::size_of::<Self>() + self.payload_len()
size_of::<Self>() + self.payload_len()
}

/// Updates the header with the given `total_size`.
Expand Down Expand Up @@ -334,15 +333,20 @@ impl<H: Header> DynSizedStructure<H> {
let ptr = bytes.as_ptr().cast::<H>();
let hdr = unsafe { &*ptr };

if hdr.payload_len() > bytes.len() {
return Err(MemoryError::InvalidReportedTotalSize);
let payload_len = hdr.payload_len();
let total_size = size_of::<H>() + payload_len;
if total_size > bytes.len() {
return Err(MemoryError::InvalidReportedTotalSize(
total_size,
bytes.len(),
));
}

// At this point we know that the memory slice fulfills the base
// assumptions and requirements. Now, we safety can create the fat
// pointer.

let dst_size = hdr.payload_len();
let dst_size = payload_len;
// Create fat pointer for the DST.
let ptr = ptr_meta::from_raw_parts(ptr.cast(), dst_size);
let reference = unsafe { &*ptr };
Expand Down Expand Up @@ -401,23 +405,21 @@ impl<H: Header> DynSizedStructure<H> {
/// # Panics
/// This panics if there is a size mismatch. However, this should never be
/// the case if all types follow their documented requirements.
///
/// [`size_of_val`]: mem::size_of_val
pub fn cast<T: MaybeDynSized<Header = H> + ?Sized>(&self) -> &T {
// Thin or fat pointer, depending on type.
// However, only thin ptr is needed.
let base_ptr = ptr::addr_of!(*self);

// This should be a compile-time assertion. However, this is the best
// location to place it for now.
assert!(T::BASE_SIZE >= mem::size_of::<H>());
assert!(T::BASE_SIZE >= size_of::<H>());

let t_dst_size = T::dst_len(self.header());
// Creates thin or fat pointer, depending on type.
let t_ptr = ptr_meta::from_raw_parts(base_ptr.cast(), t_dst_size);
let t_ref = unsafe { &*t_ptr };

assert_eq!(mem::size_of_val(self), mem::size_of_val(t_ref));
assert_eq!(size_of_val(self), size_of_val(t_ref));

t_ref
}
Expand All @@ -436,15 +438,20 @@ pub enum MemoryError {
/// type.
#[error("memory range is shorter than the size of the header structure")]
ShorterThanHeader,
/// The size is insufficient to contain at least a valid minimal structure.
#[error("memory range is shorter than the size of the header structure")]
SizeInsufficient(usize /* actual */, usize /* expected */),
/// The buffer misses the terminating padding to the next alignment
/// boundary. The padding is relevant to satisfy Rustc/Miri, but also the
/// spec mandates that the padding is added.
#[error("memory is missing required padding")]
MissingPadding,
/// The size-property has an illegal value that can't be fulfilled with the
/// given bytes.
#[error("the header reports an invalid total size")]
InvalidReportedTotalSize,
#[error(
"header reports an invalid total size of 0x{0:x} while only 0x{1:x} bytes are available"
)]
InvalidReportedTotalSize(usize /* actual */, usize /* expected */),
}

/// Increases the given size to the next alignment boundary, if it is not a
Expand Down Expand Up @@ -487,7 +494,7 @@ mod tests {
impl MaybeDynSized for CustomSizedTag {
type Header = DummyTestHeader;

const BASE_SIZE: usize = mem::size_of::<Self>();
const BASE_SIZE: usize = size_of::<Self>();

fn dst_len(_header: &DummyTestHeader) -> Self::Metadata {}
}
Expand All @@ -502,7 +509,7 @@ mod tests {
let tag = DynSizedStructure::ref_from_slice(bytes.borrow()).unwrap();
let custom_tag = tag.cast::<CustomSizedTag>();

assert_eq!(mem::size_of_val(custom_tag), 16);
assert_eq!(size_of_val(custom_tag), 16);
assert_eq!(custom_tag.a, 0xdead_beef);
assert_eq!(custom_tag.b, 0x1337_1337);
}
Expand Down Expand Up @@ -530,4 +537,24 @@ mod tests {
assert_eq!(tag.header().typ(), 0x1337);
assert_eq!(tag.header().size(), 18);
}

#[test]
fn test_ref_from_slice_rejects_oversized_header() {
#[rustfmt::skip]
let bytes = AlignedBytes::new(
[
0x37, 0x13, 0, 0,
/* Tag size */
24, 0, 0, 0,
/* Only 8 bytes payload plus padding are available. */
0, 1, 2, 3,
4, 5, 6, 7,
],
);

assert_eq!(
DynSizedStructure::<DummyTestHeader>::ref_from_slice(bytes.borrow()),
Err(MemoryError::InvalidReportedTotalSize(24, 16))
);
}
}
7 changes: 3 additions & 4 deletions multiboot2-common/src/tag.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
//! Module for the traits [`MaybeDynSized`] and [`Tag`].

use crate::{BytesRef, DynSizedStructure, Header};
use core::mem;
use core::slice;
use ptr_meta::Pointee;

Expand Down Expand Up @@ -55,7 +54,7 @@ pub trait MaybeDynSized: Pointee {
/// Returns the payload, i.e., all memory that is not occupied by the
/// [`Header`] of the type.
fn payload(&self) -> &[u8] {
let from = mem::size_of::<Self::Header>();
let from = size_of::<Self::Header>();
&self.as_bytes()[from..]
}

Expand All @@ -65,7 +64,7 @@ pub trait MaybeDynSized: Pointee {
fn as_bytes(&self) -> BytesRef<'_, Self::Header> {
let ptr = core::ptr::addr_of!(*self);
// Actual tag size with optional terminating padding.
let size = mem::size_of_val(self);
let size = size_of_val(self);
let slice = unsafe { slice::from_raw_parts(ptr.cast::<u8>(), size) };
// Unwrap is fine as this type can't exist without the underlying memory
// guarantees.
Expand Down Expand Up @@ -95,7 +94,7 @@ pub trait Tag: MaybeDynSized {
impl<H: Header> MaybeDynSized for DynSizedStructure<H> {
type Header = H;

const BASE_SIZE: usize = mem::size_of::<H>();
const BASE_SIZE: usize = size_of::<H>();

fn dst_len(header: &Self::Header) -> Self::Metadata {
header.payload_len()
Expand Down
10 changes: 4 additions & 6 deletions multiboot2-common/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

use crate::{Header, MaybeDynSized, Tag};
use core::borrow::Borrow;
use core::mem;
use core::ops::Deref;

/// Helper to 8-byte align the underlying bytes, as mandated in the Multiboot2
Expand Down Expand Up @@ -71,7 +70,7 @@ impl DummyTestHeader {

impl Header for DummyTestHeader {
fn payload_len(&self) -> usize {
self.size as usize - mem::size_of::<Self>()
self.size as usize - size_of::<Self>()
}

fn set_size(&mut self, total_size: usize) {
Expand All @@ -87,7 +86,7 @@ pub struct DummyDstTag {
}

impl DummyDstTag {
const BASE_SIZE: usize = mem::size_of::<DummyTestHeader>();
const BASE_SIZE: usize = size_of::<DummyTestHeader>();

#[must_use]
pub const fn header(&self) -> &DummyTestHeader {
Expand All @@ -103,7 +102,7 @@ impl DummyDstTag {
impl MaybeDynSized for DummyDstTag {
type Header = DummyTestHeader;

const BASE_SIZE: usize = mem::size_of::<DummyTestHeader>();
const BASE_SIZE: usize = size_of::<DummyTestHeader>();

fn dst_len(header: &Self::Header) -> Self::Metadata {
header.size as usize - Self::BASE_SIZE
Expand All @@ -117,7 +116,6 @@ impl Tag for DummyDstTag {

#[cfg(test)]
mod tests {
use core::mem;
use core::ptr::addr_of;

use crate::ALIGNMENT;
Expand All @@ -126,7 +124,7 @@ mod tests {

#[test]
fn abi() {
assert_eq!(mem::align_of::<AlignedBytes<0>>(), ALIGNMENT);
assert_eq!(align_of::<AlignedBytes<0>>(), ALIGNMENT);

let bytes = AlignedBytes([0]);
assert_eq!(bytes.as_ptr().align_offset(8), 0);
Expand Down
Loading
Loading