Browse Source

multiboot2: Memory-safe new_boxed

This will replace the existing BoxedDst typ, which has UB.
Philipp Schuster 8 months ago
parent
commit
5148bbb3dc
2 changed files with 49 additions and 2 deletions
  1. 1 1
      multiboot2/src/lib.rs
  2. 48 1
      multiboot2/src/util.rs

+ 1 - 1
multiboot2/src/lib.rs

@@ -100,7 +100,7 @@ pub use smbios::SmbiosTag;
 pub use tag::TagHeader;
 pub use tag_trait::TagTrait;
 pub use tag_type::{TagType, TagTypeId};
-pub use util::{parse_slice_as_string, StringError};
+pub use util::{new_boxed, parse_slice_as_string, StringError};
 pub use vbe_info::{
     VBECapabilities, VBEControlInfo, VBEDirectColorAttributes, VBEField, VBEInfoTag,
     VBEMemoryModel, VBEModeAttributes, VBEModeInfo, VBEWindowAttributes,

+ 48 - 1
multiboot2/src/util.rs

@@ -1,9 +1,13 @@
 //! Various utilities.
 
-use crate::ALIGNMENT;
+use crate::tag::GenericTag;
+use crate::{TagHeader, TagTrait, TagType, ALIGNMENT};
 use core::fmt;
 use core::fmt::{Display, Formatter};
 use core::str::Utf8Error;
+use core::{ptr, slice};
+#[cfg(feature = "builder")]
+use {alloc::alloc::Layout, alloc::boxed::Box};
 
 /// Error type describing failures when parsing the string from a tag.
 #[derive(Debug, PartialEq, Eq, Clone)]
@@ -31,6 +35,38 @@ impl core::error::Error for StringError {
     }
 }
 
+/// Creates a new tag implementing [`TagTrait`] on the heap. This works for
+/// sized and unsized tags. However, it only makes sense to use this for tags
+/// that are DSTs (unsized), as for the sized ones, you can call a regular
+/// constructor and box the result.
+///
+/// # Parameters
+/// - `additional_bytes`: All bytes apart from the default [`TagHeader`] that
+///                       are included into the tag.
+#[cfg(feature = "alloc")]
+pub fn new_boxed<T: TagTrait + ?Sized>(additional_bytes: &[u8]) -> Box<T> {
+    let size = size_of::<TagHeader>() + additional_bytes.iter().len();
+    let alloc_size = increase_to_alignment(size);
+    let layout = Layout::from_size_align(alloc_size, ALIGNMENT).unwrap();
+    let heap_ptr = unsafe { alloc::alloc::alloc(layout) };
+    assert!(!heap_ptr.is_null());
+
+    unsafe {
+        heap_ptr.cast::<u32>().write(T::ID.val());
+        heap_ptr.cast::<u32>().add(1).write(size as u32);
+    }
+    unsafe {
+        let ptr = heap_ptr.add(size_of::<TagHeader>());
+        ptr::copy_nonoverlapping(additional_bytes.as_ptr(), ptr, additional_bytes.len());
+    }
+
+    let header = unsafe { heap_ptr.cast::<TagHeader>().as_ref() }.unwrap();
+
+    let ptr = ptr_meta::from_raw_parts_mut(heap_ptr.cast(), T::dst_len(header));
+
+    unsafe { Box::from_raw(ptr) }
+}
+
 /// Parses the provided byte sequence as Multiboot string, which maps to a
 /// [`str`].
 pub fn parse_slice_as_string(bytes: &[u8]) -> Result<&str, StringError> {
@@ -52,6 +88,8 @@ pub const fn increase_to_alignment(size: usize) -> usize {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::tag::GenericTag;
+    use crate::CommandLineTag;
 
     #[test]
     fn test_parse_slice_as_string() {
@@ -87,4 +125,13 @@ mod tests {
         assert_eq!(increase_to_alignment(8), 8);
         assert_eq!(increase_to_alignment(9), 16);
     }
+
+    #[test]
+    fn test_new_boxed() {
+        let tag = new_boxed::<GenericTag>(&[0, 1, 2, 3]);
+        assert_eq!(tag.header().typ, GenericTag::ID);
+        {}
+        let tag = new_boxed::<CommandLineTag>("hello\0".as_bytes());
+        assert_eq!(tag.cmdline(), Ok("hello"));
+    }
 }