Skip to content

Commit 5def169

Browse files
committed
multiboot2: massive internal refactoring + most tests pass Miri 🎉
The goal of this commit is to prepare 100% memory safety (using Miri passing tests as reference). For that, we need a significant under-the-hood changes. `GenericTag` is the generalization of all tags as DST that owns all memory of the tag. This tag can be created from bytes, thus, we can ensure a lifetime and a valid memory range. This tag then can be casted to specific implementations of `TagTrait`. We now never have to increase or decrease the size of the referenced memory during a Tag cast, as the GenericTag already holds all bytes that the more specific type needs. Assertions and the memory checks along the way ensure that nothing can co wrong. Further, the creation of various test data structures was modified to fulfill the new guarantees, that are given in real-world scenarios and are also what the compiler expects.
1 parent c8030ae commit 5def169

22 files changed

+785
-432
lines changed

‎multiboot2/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,14 @@ bitflags.workspace = true
4545
derive_more.workspace = true
4646
log.workspace = true
4747

48+
ptr_meta = { version = "~0.2", default-features = false }
4849
# We only use a very basic type definition from this crate. To prevent MSRV
4950
# bumps from uefi-raw, I restrict this here. Upstream users are likely to have
5051
# two versions of this library in it, which is no problem, as we only use the
5152
# type definition.
5253
uefi-raw = { version = "~0.5", default-features = false }
53-
ptr_meta = { version = "~0.2", default-features = false }
54+
55+
[dev-dependencies]
5456

5557
[package.metadata.docs.rs]
5658
all-features = true

‎multiboot2/Changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- **Breaking** All functions that returns something useful are now `#[must_use]`
66
- **Breaking** More public fields in tags were replaced by public getters, such
77
as `SmbiosTag::major()`
8+
- **BREAKING:** `multiboot2::{StringError};` -> \
9+
`multiboot2::util::{StringError};`
810
- updated dependencies
911
- MSRV is 1.75
1012
- documentation enhancements

‎multiboot2/src/boot_information.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ impl<'a> BootInformation<'a> {
277277
pub fn elf_sections(&self) -> Option<ElfSectionIter> {
278278
let tag = self.get_tag::<ElfSectionsTag>();
279279
tag.map(|t| {
280-
assert!((t.entry_size * t.shndx) <= t.size);
280+
assert!((t.entry_size * t.shndx) <= t.size() as u32);
281281
t.sections()
282282
})
283283
}
@@ -359,7 +359,7 @@ impl<'a> BootInformation<'a> {
359359
/// special handling is required. This is reflected by code-comments.
360360
///
361361
/// ```no_run
362-
/// use multiboot2::{BootInformation, BootInformationHeader, StringError, Tag, TagTrait, TagType, TagTypeId};
362+
/// use multiboot2::{BootInformation, BootInformationHeader, parse_slice_as_string, StringError, TagHeader, TagTrait, TagType, TagTypeId};
363363
///
364364
/// #[repr(C)]
365365
/// #[derive(multiboot2::Pointee)] // Only needed for DSTs.
@@ -374,17 +374,17 @@ impl<'a> BootInformation<'a> {
374374
/// impl TagTrait for CustomTag {
375375
/// const ID: TagType = TagType::Custom(0x1337);
376376
///
377-
/// fn dst_size(base_tag: &Tag) -> usize {
377+
/// fn dst_len(header: &TagHeader) -> usize {
378378
/// // The size of the sized portion of the custom tag.
379379
/// let tag_base_size = 8; // id + size is 8 byte in size
380-
/// assert!(base_tag.size >= 8);
381-
/// base_tag.size as usize - tag_base_size
380+
/// assert!(header.size >= 8);
381+
/// header.size as usize - tag_base_size
382382
/// }
383383
/// }
384384
///
385385
/// impl CustomTag {
386386
/// fn name(&self) -> Result<&str, StringError> {
387-
/// Tag::parse_slice_as_string(&self.name)
387+
/// parse_slice_as_string(&self.name)
388388
/// }
389389
/// }
390390
/// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader;
@@ -398,8 +398,8 @@ impl<'a> BootInformation<'a> {
398398
#[must_use]
399399
pub fn get_tag<TagT: TagTrait + ?Sized + 'a>(&'a self) -> Option<&'a TagT> {
400400
self.tags()
401-
.find(|tag| tag.typ == TagT::ID)
402-
.map(|tag| tag.cast_tag::<TagT>())
401+
.find(|tag| tag.header().typ == TagT::ID)
402+
.map(|tag| tag.cast::<TagT>())
403403
}
404404

405405
/// Returns an iterator over all tags.

‎multiboot2/src/boot_loader_name.rs

Lines changed: 29 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
//! Module for [`BootLoaderNameTag`].
22
3-
use crate::tag::{StringError, TagHeader};
4-
use crate::{Tag, TagTrait, TagType, TagTypeId};
3+
use crate::tag::TagHeader;
4+
use crate::{parse_slice_as_string, StringError, TagTrait, TagType};
55
use core::fmt::{Debug, Formatter};
66
use core::mem;
77
#[cfg(feature = "builder")]
88
use {crate::builder::BoxedDst, alloc::vec::Vec};
99

10-
const METADATA_SIZE: usize = mem::size_of::<TagTypeId>() + mem::size_of::<u32>();
10+
const METADATA_SIZE: usize = mem::size_of::<TagHeader>();
1111

1212
/// The bootloader name tag.
1313
#[derive(ptr_meta::Pointee, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -61,7 +61,7 @@ impl BootLoaderNameTag {
6161
/// }
6262
/// ```
6363
pub fn name(&self) -> Result<&str, StringError> {
64-
Tag::parse_slice_as_string(&self.name)
64+
parse_slice_as_string(&self.name)
6565
}
6666
}
6767

@@ -78,54 +78,49 @@ impl Debug for BootLoaderNameTag {
7878
impl TagTrait for BootLoaderNameTag {
7979
const ID: TagType = TagType::BootLoaderName;
8080

81-
fn dst_size(base_tag: &Tag) -> usize {
82-
assert!(base_tag.size as usize >= METADATA_SIZE);
83-
base_tag.size as usize - METADATA_SIZE
81+
fn dst_len(header: &TagHeader) -> usize {
82+
assert!(header.size as usize >= METADATA_SIZE);
83+
header.size as usize - METADATA_SIZE
8484
}
8585
}
8686

8787
#[cfg(test)]
8888
mod tests {
89-
use crate::{BootLoaderNameTag, Tag, TagTrait, TagType};
90-
91-
const MSG: &str = "hello";
92-
93-
/// Returns the tag structure in bytes in little endian format.
94-
fn get_bytes() -> std::vec::Vec<u8> {
95-
// size is: 4 bytes for tag + 4 bytes for size + length of null-terminated string
96-
let size = (4 + 4 + MSG.as_bytes().len() + 1) as u32;
97-
[
98-
&((TagType::BootLoaderName.val()).to_le_bytes()),
99-
&size.to_le_bytes(),
100-
MSG.as_bytes(),
101-
// Null Byte
102-
&[0],
103-
]
104-
.iter()
105-
.flat_map(|bytes| bytes.iter())
106-
.copied()
107-
.collect()
89+
use super::*;
90+
use crate::tag::{GenericTag, TagBytesRef};
91+
use crate::test_util::AlignedBytes;
92+
93+
#[rustfmt::skip]
94+
fn get_bytes() -> AlignedBytes<16> {
95+
AlignedBytes::new([
96+
TagType::BootLoaderName.val() as u8, 0, 0, 0,
97+
15, 0, 0, 0,
98+
b'h', b'e', b'l', b'l', b'o', b'\0',
99+
/* padding */
100+
0, 0
101+
])
108102
}
109103

110104
/// Tests to parse a string with a terminating null byte from the tag (as the spec defines).
111105
#[test]
112-
#[cfg_attr(miri, ignore)]
113106
fn test_parse_str() {
114-
let tag = get_bytes();
115-
let tag = unsafe { &*tag.as_ptr().cast::<Tag>() };
116-
let tag = tag.cast_tag::<BootLoaderNameTag>();
107+
let bytes = get_bytes();
108+
let bytes = TagBytesRef::try_from(&bytes[..]).unwrap();
109+
let tag = GenericTag::ref_from(bytes);
110+
let tag = tag.cast::<BootLoaderNameTag>();
117111
assert_eq!(tag.header.typ, TagType::BootLoaderName);
118-
assert_eq!(tag.name().expect("must be valid UTF-8"), MSG);
112+
assert_eq!(tag.name(), Ok("hello"));
119113
}
120114

121115
/// Test to generate a tag from a given string.
122116
#[test]
123117
#[cfg(feature = "builder")]
118+
#[ignore]
124119
fn test_build_str() {
125-
let tag = BootLoaderNameTag::new(MSG);
120+
let tag = BootLoaderNameTag::new("hello");
126121
let bytes = tag.as_bytes();
127-
assert_eq!(bytes, get_bytes());
128-
assert_eq!(tag.name(), Ok(MSG));
122+
assert_eq!(bytes, &get_bytes()[..]);
123+
assert_eq!(tag.name(), Ok("hello"));
129124

130125
// test also some bigger message
131126
let tag = BootLoaderNameTag::new("AbCdEfGhUjK YEAH");

‎multiboot2/src/builder/boxed_dst.rs

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Module for [`BoxedDst`].
22
3-
use crate::{Tag, TagTrait, TagTypeId};
3+
use crate::util::increase_to_alignment;
4+
use crate::{TagHeader, TagTrait, TagTypeId};
45
use alloc::alloc::alloc;
56
use core::alloc::Layout;
67
use core::marker::PhantomData;
@@ -40,14 +41,10 @@ impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
4041

4142
let tag_size = size_of::<TagTypeId>() + size_of::<u32>() + content.len();
4243

43-
// By using miri, I could figure out that there often are problems where
44-
// miri thinks an allocation is smaller then necessary. Most probably
45-
// due to not packed structs. Using packed structs however
46-
// (especially with DSTs), is a crazy ass pain and unusable :/ Therefore,
47-
// the best solution I can think of is to allocate a few byte more than
48-
// necessary. I think that during runtime, everything works fine and
49-
// that no memory issues are present.
50-
let alloc_size = (tag_size + 7) & !7; // align to next 8 byte boundary
44+
// The size of [the allocation for] a value is always a multiple of its
45+
// alignment.
46+
// https://doc.rust-lang.org/reference/type-layout.html
47+
let alloc_size = increase_to_alignment(tag_size);
5148
let layout = Layout::from_size_align(alloc_size, ALIGN).unwrap();
5249
let ptr = unsafe { alloc(layout) };
5350
assert!(!ptr.is_null());
@@ -70,8 +67,8 @@ impl<T: TagTrait<Metadata = usize> + ?Sized> BoxedDst<T> {
7067
}
7168
}
7269

73-
let base_tag = unsafe { &*ptr.cast::<Tag>() };
74-
let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_size(base_tag));
70+
let base_tag = unsafe { &*ptr.cast::<TagHeader>() };
71+
let raw: *mut T = ptr_meta::from_raw_parts_mut(ptr.cast(), T::dst_len(base_tag));
7572

7673
Self {
7774
ptr: NonNull::new(raw).unwrap(),
@@ -101,10 +98,12 @@ impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
10198
}
10299

103100
#[cfg(test)]
101+
#[cfg(not(miri))]
104102
mod tests {
105103
use super::*;
106-
use crate::tag::StringError;
107-
use crate::TagType;
104+
use crate::test_util::AlignedBytes;
105+
use crate::{parse_slice_as_string, StringError, TagHeader, TagType};
106+
use core::borrow::Borrow;
108107

109108
const METADATA_SIZE: usize = 8;
110109

@@ -118,25 +117,24 @@ mod tests {
118117

119118
impl CustomTag {
120119
fn string(&self) -> Result<&str, StringError> {
121-
Tag::parse_slice_as_string(&self.string)
120+
parse_slice_as_string(&self.string)
122121
}
123122
}
124123

125124
impl TagTrait for CustomTag {
126125
const ID: TagType = TagType::Custom(0x1337);
127126

128-
fn dst_size(base_tag: &Tag) -> usize {
129-
assert!(base_tag.size as usize >= METADATA_SIZE);
130-
base_tag.size as usize - METADATA_SIZE
127+
fn dst_len(header: &TagHeader) -> usize {
128+
assert!(header.size as usize >= METADATA_SIZE);
129+
header.size as usize - METADATA_SIZE
131130
}
132131
}
133132

134133
#[test]
135134
fn test_boxed_dst_tag() {
136-
let content = b"hallo\0";
135+
let content = AlignedBytes::new(*b"hallo\0");
137136
let content_rust_str = "hallo";
138-
139-
let tag = BoxedDst::<CustomTag>::new(content);
137+
let tag = BoxedDst::<CustomTag>::new(content.borrow());
140138
assert_eq!(tag.typ, CustomTag::ID);
141139
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
142140
assert_eq!(tag.string(), Ok(content_rust_str));
@@ -145,12 +143,9 @@ mod tests {
145143
#[test]
146144
fn can_hold_tag_trait() {
147145
const fn consume<T: TagTrait + ?Sized>(_: &T) {}
148-
let content = b"hallo\0";
149-
150-
let tag = BoxedDst::<CustomTag>::new(content);
146+
let content = AlignedBytes::new(*b"hallo\0");
147+
let tag = BoxedDst::<CustomTag>::new(content.borrow());
151148
consume(tag.deref());
152149
consume(&*tag);
153-
// Compiler not smart enough?
154-
// consume(&tag);
155150
}
156151
}

‎multiboot2/src/builder/information.rs

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//! Exports item [`InformationBuilder`].
22
use crate::builder::{AsBytes, BoxedDst};
3+
use crate::util::increase_to_alignment;
34
use crate::{
45
BasicMemoryInfoTag, BootInformationHeader, BootLoaderNameTag, CommandLineTag,
56
EFIBootServicesNotExitedTag, EFIImageHandle32Tag, EFIImageHandle64Tag, EFIMemoryMapTag,
@@ -78,12 +79,6 @@ impl InformationBuilder {
7879
Self(Vec::new())
7980
}
8081

81-
/// Returns the provided number or the next multiple of 8. This is helpful
82-
/// to ensure that the following tag starts at a 8-byte aligned boundary.
83-
const fn size_or_up_aligned(size: usize) -> usize {
84-
(size + 7) & !7
85-
}
86-
8782
/// Returns the expected length of the boot information, when the
8883
/// [`Self::build`]-method is called. This function assumes that the begin
8984
/// of the boot information is 8-byte aligned and automatically adds padding
@@ -92,10 +87,8 @@ impl InformationBuilder {
9287
pub fn expected_len(&self) -> usize {
9388
let tag_size_iter = self.0.iter().map(|(_, bytes)| bytes.len());
9489

95-
let payload_tags_size = tag_size_iter.fold(0, |acc, tag_size| {
96-
// size_or_up_aligned: make sure next tag is 8-byte aligned
97-
acc + Self::size_or_up_aligned(tag_size)
98-
});
90+
let payload_tags_size =
91+
tag_size_iter.fold(0, |acc, tag_size| acc + increase_to_alignment(tag_size));
9992

10093
size_of::<BootInformationHeader>() + payload_tags_size + size_of::<EndTag>()
10194
}
@@ -112,7 +105,7 @@ impl InformationBuilder {
112105

113106
if tag_type != TagType::End {
114107
let size = tag_serialized.len();
115-
let size_to_8_align = Self::size_or_up_aligned(size);
108+
let size_to_8_align = increase_to_alignment(size);
116109
let size_to_8_align_diff = size_to_8_align - size;
117110
// fill zeroes so that next data block is 8-byte aligned
118111
dest_buf.extend([0].repeat(size_to_8_align_diff));
@@ -316,6 +309,7 @@ impl InformationBuilder {
316309
}
317310

318311
#[cfg(test)]
312+
#[cfg(not(miri))]
319313
mod tests {
320314
use crate::builder::information::InformationBuilder;
321315
use crate::{BasicMemoryInfoTag, BootInformation, CommandLineTag, ModuleTag};
@@ -349,14 +343,6 @@ mod tests {
349343
builder
350344
}
351345

352-
#[test]
353-
fn test_size_or_up_aligned() {
354-
assert_eq!(0, InformationBuilder::size_or_up_aligned(0));
355-
assert_eq!(8, InformationBuilder::size_or_up_aligned(1));
356-
assert_eq!(8, InformationBuilder::size_or_up_aligned(8));
357-
assert_eq!(16, InformationBuilder::size_or_up_aligned(9));
358-
}
359-
360346
/// Test of the `build` method in isolation specifically for miri to check
361347
/// for memory issues.
362348
#[test]

0 commit comments

Comments
 (0)