1 //! This is a utility module with helper methods for allocations/memory.
2
3 use crate::data_types::Align;
4 use crate::{Error, Result, ResultExt, Status};
5 use ::alloc::boxed::Box;
6 use core::alloc::Layout;
7 use core::fmt::Debug;
8 use core::slice;
9
10 #[cfg(not(feature = "unstable"))]
11 use ::alloc::alloc::{alloc, dealloc};
12
13 #[cfg(feature = "unstable")]
14 use {core::alloc::Allocator, core::ptr::NonNull};
15
16 /// Helper to return owned versions of certain UEFI data structures on the heap in a [`Box`]. This
17 /// function is intended to wrap low-level UEFI functions of this crate that
18 /// - can consume an empty buffer without a panic to get the required buffer size in the errors
19 /// payload,
20 /// - consume a mutable reference to a buffer that will be filled with some data if the provided
21 /// buffer size is sufficient, and
22 /// - return a mutable typed reference that points to the same memory as the input buffer on
23 /// success.
24 ///
25 /// # Feature `unstable` / `allocator_api`
26 /// By default, this function works with the allocator that is set as
27 /// `#[global_allocator]`. This might be UEFI allocator but depends on your
28 /// use case and how you set up the environment.
29 ///
30 /// If you activate the `unstable`-feature, all allocations uses the provided
31 /// allocator (via `allocator_api`) instead. In that case, the function takes an
32 /// additional parameter describing the specific [`Allocator`]. You can use
33 /// [`alloc::alloc::Global`] which defaults to the `#[global_allocator]`.
34 ///
35 /// [`Allocator`]: https://doc.rust-lang.org/alloc/alloc/trait.Allocator.html
36 /// [`alloc::alloc::Global`]: https://doc.rust-lang.org/alloc/alloc/struct.Global.html
make_boxed< 'a, Data: Align + ?Sized + Debug + 'a, F: FnMut(&'a mut [u8]) -> Result<&'a mut Data, Option<usize>>, #[cfg(feature = "unstable")] A: Allocator, >( mut fetch_data_fn: F, #[cfg(feature = "unstable")] allocator: A, ) -> Result<Box<Data>>37 pub(crate) fn make_boxed<
38 'a,
39 // The UEFI data structure.
40 Data: Align + ?Sized + Debug + 'a,
41 F: FnMut(&'a mut [u8]) -> Result<&'a mut Data, Option<usize>>,
42 #[cfg(feature = "unstable")] A: Allocator,
43 >(
44 // A function to read the UEFI data structure into a provided buffer.
45 mut fetch_data_fn: F,
46 #[cfg(feature = "unstable")]
47 // Allocator of the `allocator_api` feature. You can use `Global` as default.
48 allocator: A,
49 ) -> Result<Box<Data>> {
50 let required_size = match fetch_data_fn(&mut []).map_err(Error::split) {
51 // This is the expected case: the empty buffer passed in is too
52 // small, so we get the required size.
53 Err((Status::BUFFER_TOO_SMALL, Some(required_size))) => Ok(required_size),
54 // Propagate any other error.
55 Err((status, _)) => Err(Error::from(status)),
56 // Success is unexpected, return an error.
57 Ok(_) => {
58 log::debug!("Got unexpected success status");
59 Err(Error::from(Status::UNSUPPORTED))
60 }
61 }?;
62
63 // We add trailing padding because the size of a rust structure must
64 // always be a multiple of alignment.
65 let layout = Layout::from_size_align(required_size, Data::alignment())
66 .unwrap()
67 .pad_to_align();
68
69 // Allocate the buffer on the heap.
70 let heap_buf: *mut u8 = {
71 #[cfg(not(feature = "unstable"))]
72 {
73 let ptr = unsafe { alloc(layout) };
74 if ptr.is_null() {
75 return Err(Status::OUT_OF_RESOURCES.into());
76 }
77 ptr
78 }
79
80 #[cfg(feature = "unstable")]
81 allocator
82 .allocate(layout)
83 .map_err(|_| <Status as Into<Error>>::into(Status::OUT_OF_RESOURCES))?
84 .as_ptr()
85 .cast::<u8>()
86 };
87
88 // Read the data into the provided buffer.
89 let data: Result<&mut Data> = {
90 let buffer = unsafe { slice::from_raw_parts_mut(heap_buf, required_size) };
91 fetch_data_fn(buffer).discard_errdata()
92 };
93
94 // If an error occurred, deallocate the memory before returning.
95 let data: &mut Data = match data {
96 Ok(data) => data,
97 Err(err) => {
98 #[cfg(not(feature = "unstable"))]
99 unsafe {
100 dealloc(heap_buf, layout)
101 };
102 #[cfg(feature = "unstable")]
103 unsafe {
104 allocator.deallocate(NonNull::new(heap_buf).unwrap(), layout)
105 }
106 return Err(err);
107 }
108 };
109
110 let data = unsafe { Box::from_raw(data) };
111
112 Ok(data)
113 }
114
115 #[cfg(test)]
116 mod tests {
117 use super::*;
118 use crate::{ResultExt, StatusExt};
119 #[cfg(feature = "unstable")]
120 use alloc::alloc::Global;
121 use core::mem::{align_of, size_of};
122
123 /// Some simple dummy type to test [`make_boxed`].
124 #[derive(Debug)]
125 #[repr(C)]
126 struct SomeData([u8; 4]);
127
128 impl Align for SomeData {
alignment() -> usize129 fn alignment() -> usize {
130 align_of::<Self>()
131 }
132 }
133
134 /// Type wrapper that ensures an alignment of 16 for the underlying data.
135 #[derive(Debug)]
136 #[repr(C, align(16))]
137 struct Align16<T>(T);
138
139 /// Version of [`SomeData`] that has an alignment of 16.
140 type SomeDataAlign16 = Align16<SomeData>;
141
142 impl Align for SomeDataAlign16 {
alignment() -> usize143 fn alignment() -> usize {
144 align_of::<Self>()
145 }
146 }
147
148 /// Function that behaves like the other UEFI functions. It takes a
149 /// mutable reference to a buffer memory that represents a [`SomeData`]
150 /// instance.
uefi_function_stub_read<Data: Align>(buf: &mut [u8]) -> Result<&mut Data, Option<usize>>151 fn uefi_function_stub_read<Data: Align>(buf: &mut [u8]) -> Result<&mut Data, Option<usize>> {
152 let required_size = size_of::<Data>();
153
154 if buf.len() < required_size {
155 // We can use an zero-length buffer to find the required size.
156 return Status::BUFFER_TOO_SMALL.to_result_with(|| panic!(), |_| Some(required_size));
157 };
158
159 // assert alignment
160 assert_eq!(
161 buf.as_ptr() as usize % Data::alignment(),
162 0,
163 "The buffer must be correctly aligned!"
164 );
165
166 buf[0] = 1;
167 buf[1] = 2;
168 buf[2] = 3;
169 buf[3] = 4;
170
171 let data = unsafe { &mut *buf.as_mut_ptr().cast::<Data>() };
172
173 Ok(data)
174 }
175
176 // Some basic sanity checks so that we can catch problems early that miri would detect
177 // otherwise.
178 #[test]
test_some_data_type_size_constraints()179 fn test_some_data_type_size_constraints() {
180 assert_eq!(size_of::<SomeData>(), 4);
181 assert_eq!(SomeData::alignment(), 1);
182 assert_eq!(
183 size_of::<SomeDataAlign16>(),
184 16,
185 "The size must be 16 instead of 4, as in Rust the runtime size is a multiple of the alignment."
186 );
187 assert_eq!(SomeDataAlign16::alignment(), 16);
188 }
189
190 // Tests `uefi_function_stub_read` which is the foundation for the `test_make_boxed_utility`
191 // test.
192 #[test]
test_basic_stub_read()193 fn test_basic_stub_read() {
194 assert_eq!(
195 uefi_function_stub_read::<SomeData>(&mut []).status(),
196 Status::BUFFER_TOO_SMALL
197 );
198 assert_eq!(
199 *uefi_function_stub_read::<SomeData>(&mut [])
200 .unwrap_err()
201 .data(),
202 Some(4)
203 );
204
205 let mut buf: [u8; 4] = [0; 4];
206 let data: &mut SomeData = uefi_function_stub_read(&mut buf).unwrap();
207 assert_eq!(&data.0, &[1, 2, 3, 4]);
208
209 let mut buf: Align16<[u8; 16]> = Align16([0; 16]);
210 let data: &mut SomeDataAlign16 = uefi_function_stub_read(&mut buf.0).unwrap();
211 assert_eq!(&data.0 .0, &[1, 2, 3, 4]);
212 }
213
214 /// This unit tests checks the [`make_boxed`] utility. The test has different code and behavior
215 /// depending on whether the "unstable" feature is active or not.
216 #[test]
test_make_boxed_utility()217 fn test_make_boxed_utility() {
218 let fetch_data_fn = |buf| uefi_function_stub_read(buf);
219
220 #[cfg(not(feature = "unstable"))]
221 let data: Box<SomeData> = make_boxed(fetch_data_fn).unwrap();
222
223 #[cfg(feature = "unstable")]
224 let data: Box<SomeData> = make_boxed(fetch_data_fn, Global).unwrap();
225 assert_eq!(&data.0, &[1, 2, 3, 4]);
226
227 let fetch_data_fn = |buf| uefi_function_stub_read(buf);
228
229 #[cfg(not(feature = "unstable"))]
230 let data: Box<SomeDataAlign16> = make_boxed(fetch_data_fn).unwrap();
231
232 #[cfg(feature = "unstable")]
233 let data: Box<SomeDataAlign16> = make_boxed(fetch_data_fn, Global).unwrap();
234
235 assert_eq!(&data.0 .0, &[1, 2, 3, 4]);
236 }
237 }
238