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