Skip to content

Commit 9987fa3

Browse files
committed
multiboot2: Fix framebuffer code
Now, the last `#[cfg_attr(miri, ignore)]` is gone!
1 parent d325988 commit 9987fa3

File tree

4 files changed

+196
-76
lines changed

4 files changed

+196
-76
lines changed

multiboot2/Changelog.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,27 @@ corer-cases removed. Further, the builder's API was streamlined.
1010
If you are interested in the internals of the major refactorings recently taken
1111
place, please head to the documentation of `multiboot2-common`.
1212

13-
- **Breaking** The builder type is now just called `Builder`. This needs the
13+
- **Breaking:** The builder type is now just called `Builder`. This needs the
1414
`builder` feature.
15-
- **Breaking** The framebuffer tag has been marked `unsafe` as its
16-
implementation contains UB and needs rework. This originates from no longer
17-
active contributors. **Contributions are welcome.** This is the last place
18-
with UB that I am aware of after the recent intense and tough refactorings.
19-
- The trait `TagTrait` was removed and was replaced by a new `Tag` trait coming
20-
from `multiboot2-common`. This only affects you if you provide custom tag
21-
types for the library.
15+
- **Breaking:** The framebuffer tag was refactored and several bugs, memory
16+
- issues, and UB were fixed. It is now safe to use this, but some existing
17+
usages might break and need to be slightly adapted.
18+
- **Breaking:** The trait `TagTrait` was removed and was replaced by a new `Tag`
19+
trait coming from `multiboot2-common`. This only affects you if you provide
20+
custom tag types for the library.
21+
22+
**General Note on Safety and UB (TL;DR: Crate is Safe)**
23+
24+
The major refactorings of release `0.21` and `0.22` were an incredible step
25+
forward in code quality and memory safety. We have a comprehensive test coverage
26+
and all tests are passed by Miri. It might be that by using fuzzing, more
27+
corner and niche cases where UB can occur get uncovered. However, for every-day
28+
usage with sane bootloaders that do not intentionally create malformed tags, you
29+
are now absolutely good to go.
30+
31+
Sorry for all the UB that silently slept insight many parts of the code base.
32+
This is a community project that has grown over the years. But now, the code
33+
base is in excellent shape!
2234

2335
## 0.21.0 (2024-08-17)
2436

multiboot2/src/builder.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,9 @@ impl Builder {
280280
#[cfg(test)]
281281
mod tests {
282282
use super::*;
283-
use crate::framebuffer::FramebufferTypeId;
284-
use crate::{BootInformation, MemoryArea, MemoryAreaType, VBEControlInfo, VBEModeInfo};
283+
use crate::{
284+
BootInformation, FramebufferType, MemoryArea, MemoryAreaType, VBEControlInfo, VBEModeInfo,
285+
};
285286
use uefi_raw::table::boot::MemoryDescriptor;
286287

287288
#[test]
@@ -312,7 +313,7 @@ mod tests {
312313
756,
313314
1024,
314315
8,
315-
FramebufferTypeId::Text,
316+
FramebufferType::Text,
316317
))
317318
.elf_sections(ElfSectionsTag::new(0, 32, 0, &[]))
318319
.efi32(EFISdt32Tag::new(0x1000))

multiboot2/src/framebuffer.rs

Lines changed: 153 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,38 @@ use multiboot2_common::{MaybeDynSized, Tag};
1010
#[cfg(feature = "builder")]
1111
use {alloc::boxed::Box, multiboot2_common::new_boxed};
1212

13-
/// TODO this memory reader is unsafe and causes UB, according to Miri.
14-
/// We need to replace it.
15-
///
1613
/// Helper struct to read bytes from a raw pointer and increase the pointer
1714
/// automatically.
18-
struct Reader {
19-
ptr: *const u8,
15+
struct Reader<'a> {
16+
buffer: &'a [u8],
2017
off: usize,
2118
}
2219

23-
impl Reader {
24-
const fn new<T>(ptr: *const T) -> Self {
25-
Self {
26-
ptr: ptr as *const u8,
27-
off: 0,
28-
}
20+
impl<'a> Reader<'a> {
21+
const fn new(buffer: &'a [u8]) -> Self {
22+
Self { buffer, off: 0 }
2923
}
3024

3125
fn read_u8(&mut self) -> u8 {
26+
let val = self
27+
.buffer
28+
.get(self.off)
29+
.cloned()
30+
// This is not a solution I'm proud of, but at least it is safe.
31+
// The whole framebuffer tag code originally is not from me.
32+
// I hope someone from the community wants to improve this overall
33+
// functionality someday.
34+
.expect("Embedded framebuffer info should be properly sized and available");
3235
self.off += 1;
33-
unsafe { *self.ptr.add(self.off - 1) }
36+
val
3437
}
3538

3639
fn read_u16(&mut self) -> u16 {
3740
self.read_u8() as u16 | (self.read_u8() as u16) << 8
3841
}
3942

40-
fn read_u32(&mut self) -> u32 {
41-
self.read_u16() as u32 | (self.read_u16() as u32) << 16
42-
}
43-
44-
fn current_address(&self) -> usize {
45-
unsafe { self.ptr.add(self.off) as usize }
43+
const fn current_ptr(&self) -> *const u8 {
44+
unsafe { self.buffer.as_ptr().add(self.off) }
4645
}
4746
}
4847

@@ -71,15 +70,16 @@ pub struct FramebufferTag {
7170
/// Contains number of bits per pixel.
7271
bpp: u8,
7372

74-
/// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`.
75-
type_no: u8,
73+
/// The type of framebuffer. See [`FramebufferTypeId`].
74+
// TODO: Strictly speaking this causes UB for invalid values. However, no
75+
// sane bootloader puts something illegal there at the moment. When we
76+
// refactor this (newtype pattern?), we should also streamline other
77+
// parts in the code base accordingly.
78+
framebuffer_type: FramebufferTypeId,
7679

77-
// In the multiboot spec, it has this listed as a u8 _NOT_ a u16.
78-
// Reading the GRUB2 source code reveals it is in fact a u16.
79-
_reserved: u16,
80+
_padding: u16,
8081

81-
// TODO This situation is currently not properly typed. Someone needs to
82-
// look into it.
82+
/// This optional data and its meaning depend on the [`FramebufferTypeId`].
8383
buffer: [u8],
8484
}
8585

@@ -93,14 +93,16 @@ impl FramebufferTag {
9393
width: u32,
9494
height: u32,
9595
bpp: u8,
96-
buffer_type: FramebufferTypeId,
96+
buffer_type: FramebufferType,
9797
) -> Box<Self> {
9898
let header = TagHeader::new(Self::ID, 0);
9999
let address = address.to_ne_bytes();
100100
let pitch = pitch.to_ne_bytes();
101101
let width = width.to_ne_bytes();
102102
let height = height.to_ne_bytes();
103+
let buffer_type_id = buffer_type.id();
103104
let padding = [0; 2];
105+
let optional_buffer = buffer_type.serialize();
104106
new_boxed(
105107
header,
106108
&[
@@ -109,8 +111,9 @@ impl FramebufferTag {
109111
&width,
110112
&height,
111113
&[bpp],
112-
&[buffer_type as u8],
114+
&[buffer_type_id as u8],
113115
&padding,
116+
&optional_buffer,
114117
],
115118
)
116119
}
@@ -149,26 +152,33 @@ impl FramebufferTag {
149152
self.bpp
150153
}
151154

152-
/// TODO unsafe. Someone needs to fix this. This causes UB according to Miri.
153-
/// Dont forget to reenable all test usages once fixed.
154-
///
155155
/// The type of framebuffer, one of: `Indexed`, `RGB` or `Text`.
156-
///
157-
/// # Safety
158-
/// This function needs refactoring. This was never safe, since the beginning.
159-
pub unsafe fn buffer_type(&self) -> Result<FramebufferType, UnknownFramebufferType> {
160-
let mut reader = Reader::new(self.buffer.as_ptr());
161-
let typ = FramebufferTypeId::try_from(self.type_no)?;
162-
match typ {
156+
pub fn buffer_type(&self) -> Result<FramebufferType, UnknownFramebufferType> {
157+
let mut reader = Reader::new(&self.buffer);
158+
159+
// TODO: We should use the newtype pattern instead or so to properly
160+
// solve this.
161+
let fb_type_raw = self.framebuffer_type as u8;
162+
let fb_type = FramebufferTypeId::try_from(fb_type_raw)?;
163+
164+
match fb_type {
163165
FramebufferTypeId::Indexed => {
164-
let num_colors = reader.read_u32();
165-
// TODO static cast looks like UB?
166-
let palette = unsafe {
167-
slice::from_raw_parts(
168-
reader.current_address() as *const FramebufferColor,
169-
num_colors as usize,
170-
)
171-
} as &'static [FramebufferColor];
166+
// TODO we can create a struct for this and implement
167+
// DynSizedStruct for it to leverage the already existing
168+
// functionality
169+
let num_colors = reader.read_u16();
170+
171+
let palette = {
172+
// Ensure the slice can be created without causing UB
173+
assert_eq!(size_of::<FramebufferColor>(), 3);
174+
175+
unsafe {
176+
slice::from_raw_parts(
177+
reader.current_ptr().cast::<FramebufferColor>(),
178+
num_colors as usize,
179+
)
180+
}
181+
};
172182
Ok(FramebufferType::Indexed { palette })
173183
}
174184
FramebufferTypeId::RGB => {
@@ -224,8 +234,7 @@ impl Debug for FramebufferTag {
224234
f.debug_struct("FramebufferTag")
225235
.field("typ", &self.header.typ)
226236
.field("size", &self.header.size)
227-
// TODO unsafe. Fix in a follow-up commit
228-
//.field("buffer_type", &self.buffer_type())
237+
.field("buffer_type", &self.buffer_type())
229238
.field("address", &self.address)
230239
.field("pitch", &self.pitch)
231240
.field("width", &self.width)
@@ -243,12 +252,12 @@ impl PartialEq for FramebufferTag {
243252
&& self.width == { other.width }
244253
&& self.height == { other.height }
245254
&& self.bpp == { other.bpp }
246-
&& self.type_no == { other.type_no }
255+
&& self.framebuffer_type == { other.framebuffer_type }
247256
&& self.buffer == other.buffer
248257
}
249258
}
250259

251-
/// Helper struct for [`FramebufferType`].
260+
/// ABI-compatible framebuffer type.
252261
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
253262
#[repr(u8)]
254263
#[allow(clippy::upper_case_acronyms)]
@@ -282,7 +291,8 @@ impl From<FramebufferType<'_>> for FramebufferTypeId {
282291
}
283292
}
284293

285-
/// The type of framebuffer.
294+
/// Structured accessory to the provided framebuffer type that is not ABI
295+
/// compatible.
286296
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
287297
pub enum FramebufferType<'a> {
288298
/// Indexed color.
@@ -309,6 +319,46 @@ pub enum FramebufferType<'a> {
309319
Text,
310320
}
311321

322+
impl<'a> FramebufferType<'a> {
323+
#[must_use]
324+
const fn id(&self) -> FramebufferTypeId {
325+
match self {
326+
FramebufferType::Indexed { .. } => FramebufferTypeId::Indexed,
327+
FramebufferType::RGB { .. } => FramebufferTypeId::RGB,
328+
FramebufferType::Text => FramebufferTypeId::Text,
329+
}
330+
}
331+
332+
#[must_use]
333+
#[cfg(feature = "builder")]
334+
fn serialize(&self) -> alloc::vec::Vec<u8> {
335+
let mut data = alloc::vec::Vec::new();
336+
match self {
337+
FramebufferType::Indexed { palette } => {
338+
// TODO we can create a struct for this and implement
339+
// DynSizedStruct for it to leverage the already existing
340+
// functionality
341+
let num_colors = palette.len() as u16;
342+
data.extend(&num_colors.to_ne_bytes());
343+
for color in *palette {
344+
let serialized_color = [color.red, color.green, color.blue];
345+
data.extend(&serialized_color);
346+
}
347+
}
348+
FramebufferType::RGB { red, green, blue } => data.extend(&[
349+
red.position,
350+
red.size,
351+
green.position,
352+
green.size,
353+
blue.position,
354+
blue.size,
355+
]),
356+
FramebufferType::Text => {}
357+
}
358+
data
359+
}
360+
}
361+
312362
/// An RGB color type field.
313363
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
314364
#[repr(C)]
@@ -320,7 +370,9 @@ pub struct FramebufferField {
320370
pub size: u8,
321371
}
322372

323-
/// A framebuffer color descriptor in the palette.
373+
/// A framebuffer color descriptor in the palette. On the ABI level, multiple
374+
/// values are consecutively without padding bytes. The spec is not precise in
375+
/// that regard, but looking at Limine's and GRUB's source code confirm that.
324376
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
325377
#[repr(C)] // no align(8) here is correct
326378
pub struct FramebufferColor {
@@ -355,7 +407,56 @@ mod tests {
355407
#[test]
356408
#[cfg(feature = "builder")]
357409
fn create_new() {
358-
let tag = FramebufferTag::new(0x1000, 1, 1024, 1024, 8, FramebufferTypeId::Indexed);
410+
let tag = FramebufferTag::new(0x1000, 1, 1024, 1024, 8, FramebufferType::Text);
411+
// Good test for Miri
412+
dbg!(tag);
413+
414+
let tag = FramebufferTag::new(
415+
0x1000,
416+
1,
417+
1024,
418+
1024,
419+
8,
420+
FramebufferType::Indexed {
421+
palette: &[
422+
FramebufferColor {
423+
red: 255,
424+
green: 255,
425+
blue: 255,
426+
},
427+
FramebufferColor {
428+
red: 127,
429+
green: 42,
430+
blue: 73,
431+
},
432+
],
433+
},
434+
);
435+
// Good test for Miri
436+
dbg!(tag);
437+
438+
let tag = FramebufferTag::new(
439+
0x1000,
440+
1,
441+
1024,
442+
1024,
443+
8,
444+
FramebufferType::RGB {
445+
red: FramebufferField {
446+
position: 0,
447+
size: 0,
448+
},
449+
green: FramebufferField {
450+
position: 10,
451+
size: 20,
452+
},
453+
blue: FramebufferField {
454+
position: 30,
455+
size: 40,
456+
},
457+
},
458+
);
459+
// Good test for Miri
359460
dbg!(tag);
360461
}
361462
}

0 commit comments

Comments
 (0)