Skip to content

Commit 1540b64

Browse files
committed
multiboot2: refactored parsing of embedded strings in tags (name, cmdline)
1 parent 2cf6266 commit 1540b64

File tree

10 files changed

+87
-59
lines changed

10 files changed

+87
-59
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-test/bins/Cargo.lock

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

multiboot2/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Multiboot2-compliant bootloaders, such as GRUB. It supports all tags from the
66
specification including full support for the sections of ELF files. This library
77
is `no_std` and can be used in a Multiboot2-kernel.
88
"""
9-
version = "0.18.1"
9+
version = "0.19.0"
1010
authors = [
1111
"Philipp Oppermann <[email protected]>",
1212
"Calvin Lee <[email protected]>",

multiboot2/Changelog.md

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
# CHANGELOG for crate `multiboot2`
22

3-
## 0.19.0 (2023-07-14)
3+
## 0.19.0 (2023-09-XX)
4+
- **BREAKING** `TagTrait::get_dst_str_slice` renamed to
5+
`TagTrait::parse_slice_as_string` and now returns `Result<&str, StringError>`
6+
- **BREAKING** `BootLoaderNameTag::name` now returns `Result<&str, StringError>`
7+
- **BREAKING** `CommandLineTag::cmdline` now returns `Result<&str, StringError>`
8+
- **BREAKING** `ModuleTag::cmdline` now returns `Result<&str, StringError>`
9+
- Introduced new enum type `StringError`
410
- `InformationBuilder` now also allows to add custom tags. The new public method
511
`add_tag` was introduced for that.
612

multiboot2/src/boot_loader_name.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
use crate::tag::StringError;
12
use crate::{Tag, TagTrait, TagType, TagTypeId};
23
use core::fmt::{Debug, Formatter};
34
use core::mem::size_of;
4-
use core::str::Utf8Error;
55
#[cfg(feature = "builder")]
66
use {crate::builder::BoxedDst, alloc::vec::Vec};
77

@@ -45,8 +45,8 @@ impl BootLoaderNameTag {
4545
/// assert_eq!(Ok("GRUB 2.02~beta3-5"), tag.name());
4646
/// }
4747
/// ```
48-
pub fn name(&self) -> Result<&str, Utf8Error> {
49-
Tag::get_dst_str_slice(&self.name)
48+
pub fn name(&self) -> Result<&str, StringError> {
49+
Tag::parse_slice_as_string(&self.name)
5050
}
5151
}
5252

multiboot2/src/builder/boxed_dst.rs

+7-5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ impl<T: ?Sized + PartialEq> PartialEq for BoxedDst<T> {
103103
#[cfg(test)]
104104
mod tests {
105105
use super::*;
106+
use crate::tag::StringError;
106107
use crate::TagType;
107108

108109
const METADATA_SIZE: usize = 8;
@@ -116,8 +117,8 @@ mod tests {
116117
}
117118

118119
impl CustomTag {
119-
fn string(&self) -> Result<&str, core::str::Utf8Error> {
120-
Tag::get_dst_str_slice(&self.string)
120+
fn string(&self) -> Result<&str, StringError> {
121+
Tag::parse_slice_as_string(&self.string)
121122
}
122123
}
123124

@@ -132,11 +133,12 @@ mod tests {
132133

133134
#[test]
134135
fn test_boxed_dst_tag() {
135-
let content = "hallo";
136+
let content = b"hallo\0";
137+
let content_rust_str = "hallo";
136138

137-
let tag = BoxedDst::<CustomTag>::new(content.as_bytes());
139+
let tag = BoxedDst::<CustomTag>::new(content);
138140
assert_eq!(tag.typ, CustomTag::ID);
139141
assert_eq!(tag.size as usize, METADATA_SIZE + content.len());
140-
assert_eq!(tag.string(), Ok(content));
142+
assert_eq!(tag.string(), Ok(content_rust_str));
141143
}
142144
}

multiboot2/src/command_line.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
33
use crate::{Tag, TagTrait, TagType, TagTypeId};
44

5+
use crate::tag::StringError;
56
use core::fmt::{Debug, Formatter};
67
use core::mem;
78
use core::str;
8-
99
#[cfg(feature = "builder")]
1010
use {crate::builder::BoxedDst, alloc::vec::Vec};
1111

@@ -55,8 +55,8 @@ impl CommandLineTag {
5555
/// assert_eq!(Ok("/bootarg"), command_line);
5656
/// }
5757
/// ```
58-
pub fn cmdline(&self) -> Result<&str, str::Utf8Error> {
59-
Tag::get_dst_str_slice(&self.cmdline)
58+
pub fn cmdline(&self) -> Result<&str, StringError> {
59+
Tag::parse_slice_as_string(&self.cmdline)
6060
}
6161
}
6262

multiboot2/src/lib.rs

+5-6
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub use module::{ModuleIter, ModuleTag};
8282
pub use ptr_meta::Pointee;
8383
pub use rsdp::{RsdpV1Tag, RsdpV2Tag};
8484
pub use smbios::SmbiosTag;
85-
pub use tag::Tag;
85+
pub use tag::{StringError, Tag};
8686
pub use tag_trait::TagTrait;
8787
pub use tag_type::{TagType, TagTypeId};
8888
pub use vbe_info::{
@@ -445,7 +445,7 @@ impl<'a> BootInformation<'a> {
445445
///
446446
/// impl CustomTag {
447447
/// fn name(&self) -> Result<&str, Utf8Error> {
448-
/// Tag::get_dst_str_slice(&self.name)
448+
/// Tag::parse_slice_as_string(&self.name)
449449
/// }
450450
/// }
451451
/// let mbi_ptr = 0xdeadbeef as *const BootInformationHeader;
@@ -522,7 +522,7 @@ impl fmt::Debug for BootInformation<'_> {
522522
mod tests {
523523
use super::*;
524524
use crate::memory_map::MemoryAreaType;
525-
use core::str::Utf8Error;
525+
use crate::tag::StringError;
526526

527527
/// Compile time test to check if the boot information is Send and Sync.
528528
/// This test is relevant to give library users flexebility in passing the
@@ -540,7 +540,6 @@ mod tests {
540540
8, 0, 0, 0, // end tag size
541541
]);
542542
let ptr = bytes.0.as_ptr();
543-
let addr = ptr as usize;
544543
let bi = unsafe { BootInformation::load(ptr.cast()) };
545544
let bi = bi.unwrap();
546545

@@ -1602,8 +1601,8 @@ mod tests {
16021601
}
16031602

16041603
impl CustomTag {
1605-
fn name(&self) -> Result<&str, Utf8Error> {
1606-
Tag::get_dst_str_slice(&self.name)
1604+
fn name(&self) -> Result<&str, StringError> {
1605+
Tag::parse_slice_as_string(&self.name)
16071606
}
16081607
}
16091608

multiboot2/src/module.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1+
use crate::tag::StringError;
12
use crate::{Tag, TagIter, TagTrait, TagType, TagTypeId};
2-
33
use core::fmt::{Debug, Formatter};
44
use core::mem::size_of;
5-
use core::str::Utf8Error;
6-
75
#[cfg(feature = "builder")]
86
use {crate::builder::BoxedDst, alloc::vec::Vec};
97

@@ -47,8 +45,8 @@ impl ModuleTag {
4745
/// contains `"module2 /some_boot_module --test cmdline-option"`.
4846
///
4947
/// If the function returns `Err` then perhaps the memory is invalid.
50-
pub fn cmdline(&self) -> Result<&str, Utf8Error> {
51-
Tag::get_dst_str_slice(&self.cmdline)
48+
pub fn cmdline(&self) -> Result<&str, StringError> {
49+
Tag::parse_slice_as_string(&self.cmdline)
5250
}
5351

5452
/// Start address of the module.

multiboot2/src/tag.rs

+54-31
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,35 @@
44
55
use crate::{TagTrait, TagType, TagTypeId};
66
use core::fmt;
7-
use core::fmt::{Debug, Formatter};
7+
use core::fmt::{Debug, Display, Formatter};
88
use core::marker::PhantomData;
99
use core::str::Utf8Error;
1010

11+
/// Error type describing failures when parsing the string from a tag.
12+
#[derive(Debug, PartialEq, Eq, Clone)]
13+
pub enum StringError {
14+
/// There is no terminating null-byte, although the spec requires one.
15+
MissingNull(core::ffi::FromBytesUntilNulError),
16+
/// The sequence until the first NULL byte is not valid UTF-8.
17+
Utf8(Utf8Error),
18+
}
19+
20+
impl Display for StringError {
21+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
22+
write!(f, "{:?}", self)
23+
}
24+
}
25+
26+
#[cfg(feature = "unstable")]
27+
impl core::error::Error for StringError {
28+
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
29+
match self {
30+
StringError::MissingNull(e) => Some(e),
31+
StringError::Utf8(e) => Some(e),
32+
}
33+
}
34+
}
35+
1136
/// Common base structure for all tags that can be passed via the Multiboot2
1237
/// Information Structure (MBI) to a Multiboot2 payload/program/kernel.
1338
///
@@ -38,22 +63,13 @@ impl Tag {
3863
unsafe { TagTrait::from_base_tag(self) }
3964
}
4065

41-
/// Some multiboot2 tags are a DST as they end with a dynamically sized byte
42-
/// slice. This function parses this slice as [`str`] so that either a valid
43-
/// UTF-8 Rust string slice without a terminating null byte or an error is
44-
/// returned.
45-
pub fn get_dst_str_slice(bytes: &[u8]) -> Result<&str, Utf8Error> {
46-
// Determine length of string before first NUL character
47-
48-
let length = match bytes.iter().position(|ch| (*ch) == 0x00) {
49-
// Unterminated string case:
50-
None => bytes.len(),
51-
52-
// Terminated case:
53-
Some(idx) => idx,
54-
};
55-
// Convert to Rust string:
56-
core::str::from_utf8(&bytes[..length])
66+
/// Parses the provided byte sequence as Multiboot string, which maps to a
67+
/// [`str`].
68+
pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
69+
let cstr = core::ffi::CStr::from_bytes_until_nul(bytes)
70+
.map_err(|e| StringError::MissingNull(e))?;
71+
72+
cstr.to_str().map_err(|e| StringError::Utf8(e))
5773
}
5874
}
5975

@@ -128,21 +144,28 @@ mod tests {
128144
use super::*;
129145

130146
#[test]
131-
fn test_get_dst_str_slice() {
132-
// unlikely case
133-
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[]));
134-
// also unlikely case
135-
assert_eq!(Ok(""), Tag::get_dst_str_slice(&[b'\0']));
136-
// unlikely case: missing null byte. but the lib can cope with that
137-
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar"));
138-
// test that the null bytes is not included in the string slice
139-
assert_eq!(Ok("foobar"), Tag::get_dst_str_slice(b"foobar\0"));
140-
// test that C-style null string termination works as expected
141-
assert_eq!(Ok("foo"), Tag::get_dst_str_slice(b"foo\0bar"));
142-
// test invalid utf8
147+
fn parse_slice_as_string() {
148+
// empty slice is invalid
149+
assert!(matches!(
150+
Tag::parse_slice_as_string(&[]),
151+
Err(StringError::MissingNull(_))
152+
));
153+
// empty string is fine
154+
assert_eq!(Tag::parse_slice_as_string(&[0x00]), Ok(""));
155+
// reject invalid utf8
156+
assert!(matches!(
157+
Tag::parse_slice_as_string(&[0xff, 0x00]),
158+
Err(StringError::Utf8(_))
159+
));
160+
// reject missing null
143161
assert!(matches!(
144-
Tag::get_dst_str_slice(&[0xff, 0xff]),
145-
Err(Utf8Error { .. })
162+
Tag::parse_slice_as_string(b"hello"),
163+
Err(StringError::MissingNull(_))
146164
));
165+
// must not include final null
166+
assert_eq!(Tag::parse_slice_as_string(b"hello\0"), Ok("hello"));
167+
assert_eq!(Tag::parse_slice_as_string(b"hello\0\0"), Ok("hello"));
168+
// must skip everytihng after first null
169+
assert_eq!(Tag::parse_slice_as_string(b"hello\0foo"), Ok("hello"));
147170
}
148171
}

0 commit comments

Comments
 (0)