Skip to content

Commit 361449c

Browse files
committed
multiboot2: more safety for tags that hold an str
As discussed in #22 we do not want to rely on bootloaders following the specification in every case. Thus, this PR adds more safety for when parsing strings from tags.
1 parent 6ed0ca4 commit 361449c

File tree

4 files changed

+51
-28
lines changed

4 files changed

+51
-28
lines changed

multiboot2/src/boot_loader_name.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::TagType;
2+
use core::str::Utf8Error;
23

34
/// This tag contains the name of the bootloader that is booting the kernel.
45
///
@@ -9,11 +10,14 @@ use crate::TagType;
910
pub struct BootLoaderNameTag {
1011
typ: TagType,
1112
size: u32,
13+
/// Null-terminated UTF-8 string
1214
string: u8,
1315
}
1416

1517
impl BootLoaderNameTag {
1618
/// Read the name of the bootloader that is booting the kernel.
19+
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
20+
/// is invalid or the bootloader doesn't follow the spec.
1721
///
1822
/// # Examples
1923
///
@@ -23,11 +27,10 @@ impl BootLoaderNameTag {
2327
/// assert_eq!("GRUB 2.02~beta3-5", name);
2428
/// }
2529
/// ```
26-
pub fn name(&self) -> &str {
30+
pub fn name(&self) -> Result<&str, Utf8Error> {
2731
use core::{mem, slice, str};
28-
unsafe {
29-
let strlen = self.size as usize - mem::size_of::<BootLoaderNameTag>();
30-
str::from_utf8_unchecked(slice::from_raw_parts((&self.string) as *const u8, strlen))
31-
}
32+
let strlen = self.size as usize - mem::size_of::<BootLoaderNameTag>();
33+
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
34+
str::from_utf8(bytes)
3235
}
3336
}

multiboot2/src/command_line.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
//! Module for [CommandLineTag].
2+
13
use crate::TagType;
4+
use core::mem;
5+
use core::slice;
6+
use core::str::Utf8Error;
27

38
/// This tag contains the command line string.
49
///
@@ -9,11 +14,14 @@ use crate::TagType;
914
pub struct CommandLineTag {
1015
typ: TagType,
1116
size: u32,
17+
/// Null-terminated UTF-8 string
1218
string: u8,
1319
}
1420

1521
impl CommandLineTag {
1622
/// Read the command line string that is being passed to the booting kernel.
23+
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
24+
/// is invalid or the bootloader doesn't follow the spec.
1725
///
1826
/// # Examples
1927
///
@@ -23,11 +31,9 @@ impl CommandLineTag {
2331
/// assert_eq!("/bootarg", command_line);
2432
/// }
2533
/// ```
26-
pub fn command_line(&self) -> &str {
27-
use core::{mem, slice, str};
28-
unsafe {
29-
let strlen = self.size as usize - mem::size_of::<CommandLineTag>();
30-
str::from_utf8_unchecked(slice::from_raw_parts((&self.string) as *const u8, strlen))
31-
}
34+
pub fn command_line(&self) -> Result<&str, Utf8Error> {
35+
let strlen = self.size as usize - mem::size_of::<CommandLineTag>();
36+
let bytes = unsafe { slice::from_raw_parts((&self.string) as *const u8, strlen) };
37+
core::str::from_utf8(bytes)
3238
}
3339
}

multiboot2/src/lib.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -352,14 +352,14 @@ impl fmt::Debug for BootInformation {
352352
"boot_loader_name_tag",
353353
&self
354354
.boot_loader_name_tag()
355-
.map(|x| x.name())
355+
.and_then(|x| x.name().ok())
356356
.unwrap_or("<unknown>"),
357357
)
358358
.field(
359359
"command_line",
360360
&self
361361
.command_line_tag()
362-
.map(|x| x.command_line())
362+
.and_then(|x| x.command_line().ok())
363363
.unwrap_or(""),
364364
)
365365
.field("memory_areas", &self.memory_map_tag())
@@ -532,7 +532,13 @@ mod tests {
532532
assert!(bi.elf_sections_tag().is_none());
533533
assert!(bi.memory_map_tag().is_none());
534534
assert!(bi.module_tags().next().is_none());
535-
assert_eq!("name", bi.boot_loader_name_tag().unwrap().name());
535+
assert_eq!(
536+
"name",
537+
bi.boot_loader_name_tag()
538+
.expect("tag must be present")
539+
.name()
540+
.expect("must be valid utf8")
541+
);
536542
assert!(bi.command_line_tag().is_none());
537543
}
538544

@@ -1231,9 +1237,18 @@ mod tests {
12311237
assert!(bi.module_tags().next().is_none());
12321238
assert_eq!(
12331239
"GRUB 2.02~beta3-5",
1234-
bi.boot_loader_name_tag().unwrap().name()
1240+
bi.boot_loader_name_tag()
1241+
.expect("tag must be present")
1242+
.name()
1243+
.expect("must be valid utf-8")
1244+
);
1245+
assert_eq!(
1246+
"",
1247+
bi.command_line_tag()
1248+
.expect("tag must present")
1249+
.command_line()
1250+
.expect("must be valid utf-8")
12351251
);
1236-
assert_eq!("", bi.command_line_tag().unwrap().command_line());
12371252

12381253
// Test the Framebuffer tag
12391254
let fbi = bi.framebuffer_tag().unwrap();

multiboot2/src/module.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::tag_type::{Tag, TagIter, TagType};
22
use core::fmt::{Debug, Formatter};
3+
use core::str::Utf8Error;
34

45
/// This tag indicates to the kernel what boot module was loaded along with
56
/// the kernel image, and where it can be found.
@@ -10,25 +11,23 @@ pub struct ModuleTag {
1011
size: u32,
1112
mod_start: u32,
1213
mod_end: u32,
13-
/// Begin of the command line string.
14+
/// Null-terminated UTF-8 string
1415
cmdline_str: u8,
1516
}
1617

1718
impl ModuleTag {
18-
// The multiboot specification defines the module str as valid utf-8 (zero terminated string),
19-
// therefore this function produces defined behavior
20-
/// Get the cmdline of the module. If the GRUB configuration contains
21-
/// `module2 /foobar/some_boot_module --test cmdline-option`, then this method
19+
/// Returns the cmdline of the module.
20+
/// This is an null-terminated UTF-8 string. If this returns `Err` then perhaps the memory
21+
/// is invalid or the bootloader doesn't follow the spec.
22+
///
23+
/// For example: If the GRUB configuration contains
24+
/// `module2 /foobar/some_boot_module --test cmdline-option` then this method
2225
/// will return `--test cmdline-option`.
23-
pub fn cmdline(&self) -> &str {
26+
pub fn cmdline(&self) -> Result<&str, Utf8Error> {
2427
use core::{mem, slice, str};
2528
let strlen = self.size as usize - mem::size_of::<ModuleTag>();
26-
unsafe {
27-
str::from_utf8_unchecked(slice::from_raw_parts(
28-
&self.cmdline_str as *const u8,
29-
strlen,
30-
))
31-
}
29+
let bytes = unsafe { slice::from_raw_parts((&self.cmdline_str) as *const u8, strlen) };
30+
str::from_utf8(bytes)
3231
}
3332

3433
/// Start address of the module.

0 commit comments

Comments
 (0)