xref: /aosp_15_r20/bootable/libbootloader/gbl/libefi/src/allocation.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2023, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 use crate::{EfiEntry, RuntimeServices};
16 use efi_types::EFI_MEMORY_TYPE_LOADER_DATA;
17 
18 use core::mem::size_of_val;
19 use core::ptr::null_mut;
20 use core::{
21     alloc::{GlobalAlloc, Layout},
22     fmt::Write,
23 };
24 use liberror::{Error, Result};
25 use safemath::SafeNum;
26 
27 /// Implements a global allocator using `EFI_BOOT_SERVICES.AllocatePool()/FreePool()`
28 ///
29 /// To use, add this exact declaration to the application code:
30 ///
31 /// ```
32 /// #[no_mangle]
33 /// #[global_allocator]
34 /// static mut EFI_GLOBAL_ALLOCATOR: EfiAllocator = EfiState::new();
35 /// ```
36 ///
37 /// This is only useful for real UEFI applications; attempting to install the `EFI_GLOBAL_ALLOCATOR`
38 /// for host-side unit tests will cause the test to panic immediately.
39 pub struct EfiAllocator {
40     state: EfiState,
41     runtime_services: Option<RuntimeServices>,
42 }
43 
44 /// Represents the global EFI state.
45 enum EfiState {
46     /// Initial state, no UEFI entry point has been set, global hooks will not work.
47     Uninitialized,
48     /// [EfiEntry] is registered, global hooks are active.
49     Initialized(EfiEntry),
50     /// ExitBootServices has been called, global hooks will not work.
51     Exited,
52 }
53 
54 impl EfiState {
55     /// Returns a reference to the EfiEntry.
efi_entry(&self) -> Option<&EfiEntry>56     fn efi_entry(&self) -> Option<&EfiEntry> {
57         match self {
58             EfiState::Initialized(ref entry) => Some(entry),
59             _ => None,
60         }
61     }
62 }
63 
64 // This is a bit ugly, but we only expect this library to be used by our EFI application so it
65 // doesn't need to be super clean or scalable. The user has to declare the global variable
66 // exactly as written in the [EfiAllocator] docs for this to link properly.
67 extern "Rust" {
68     static mut EFI_GLOBAL_ALLOCATOR: EfiAllocator;
69 }
70 
71 /// An internal API to obtain library internal global EfiEntry and RuntimeServices.
internal_efi_entry_and_rt( ) -> (Option<&'static EfiEntry>, Option<&'static RuntimeServices>)72 pub(crate) fn internal_efi_entry_and_rt(
73 ) -> (Option<&'static EfiEntry>, Option<&'static RuntimeServices>) {
74     // SAFETY:
75     // EFI_GLOBAL_ALLOCATOR is only read by `internal_efi_entry_and_rt()` and modified by
76     // `init_efi_global_alloc()` and `exit_efi_global_alloc()`. The safety requirements of
77     // `init_efi_global_alloc()` and `exit_efi_global_alloc()` mandate that there can be no EFI
78     // event/notification/interrupt that can be triggered when they are called. This suggests that
79     // there cannot be concurrent read and modification on `EFI_GLOBAL_ALLOCATOR` possible. Thus its
80     // access is safe from race condition.
81     unsafe { EFI_GLOBAL_ALLOCATOR.get_efi_entry_and_rt() }
82 }
83 
84 /// Try to print via `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL` in `EFI_SYSTEM_TABLE.ConOut`.
85 ///
86 /// Errors are ignored.
87 #[macro_export]
88 macro_rules! efi_try_print {
89     ($( $x:expr ),* $(,)? ) => {
90         {
91             let _ = (|| -> Result<()> {
92                 if let Some(entry) = crate::allocation::internal_efi_entry_and_rt().0 {
93                     write!(entry.system_table_checked()?.con_out()?, $($x,)*)?;
94                 }
95                 Ok(())
96             })();
97         }
98     };
99 }
100 
101 /// Initializes global allocator.
102 ///
103 /// # Safety
104 ///
105 /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when
106 /// there is no event/notification function that can be triggered or modify it. Otherwise there
107 /// is a risk of race condition.
init_efi_global_alloc(efi_entry: EfiEntry) -> Result<()>108 pub(crate) unsafe fn init_efi_global_alloc(efi_entry: EfiEntry) -> Result<()> {
109     // SAFETY: See SAFETY of `internal_efi_entry_and_rt()`
110     unsafe {
111         EFI_GLOBAL_ALLOCATOR.runtime_services =
112             efi_entry.system_table_checked().and_then(|v| v.runtime_services_checked()).ok();
113         match EFI_GLOBAL_ALLOCATOR.state {
114             EfiState::Uninitialized => {
115                 EFI_GLOBAL_ALLOCATOR.state = EfiState::Initialized(efi_entry);
116                 Ok(())
117             }
118             _ => Err(Error::AlreadyStarted),
119         }
120     }
121 }
122 
123 /// Internal API to invalidate global allocator after ExitBootService().
124 ///
125 /// # Safety
126 ///
127 /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when
128 /// there is no event/notification function that can be triggered or modify it. Otherwise there
129 /// is a risk of race condition.
exit_efi_global_alloc()130 pub(crate) unsafe fn exit_efi_global_alloc() {
131     // SAFETY: See SAFETY of `internal_efi_entry_and_rt()`
132     unsafe {
133         EFI_GLOBAL_ALLOCATOR.state = EfiState::Exited;
134     }
135 }
136 
137 impl EfiAllocator {
138     /// Creates a new instance.
new() -> Self139     pub const fn new() -> Self {
140         Self { state: EfiState::Uninitialized, runtime_services: None }
141     }
142 
143     /// Gets EfiEntry and RuntimeServices
get_efi_entry_and_rt(&self) -> (Option<&EfiEntry>, Option<&RuntimeServices>)144     fn get_efi_entry_and_rt(&self) -> (Option<&EfiEntry>, Option<&RuntimeServices>) {
145         (self.state.efi_entry(), self.runtime_services.as_ref())
146     }
147 
148     /// Allocate memory via EFI_BOOT_SERVICES.
allocate(&self, size: usize) -> *mut u8149     fn allocate(&self, size: usize) -> *mut u8 {
150         self.state
151             .efi_entry()
152             .ok_or(Error::InvalidState)
153             .and_then(|v| v.system_table_checked())
154             .and_then(|v| v.boot_services_checked())
155             .and_then(|v| v.allocate_pool(EFI_MEMORY_TYPE_LOADER_DATA, size))
156             .inspect_err(|e| efi_try_print!("failed to allocate: {e}"))
157             .unwrap_or(null_mut()) as _
158     }
159 
160     /// Deallocate memory previously allocated by `Self::allocate()`.
161     ///
162     /// Errors are logged but ignored.
deallocate(&self, ptr: *mut u8)163     fn deallocate(&self, ptr: *mut u8) {
164         match self.state.efi_entry() {
165             Some(ref entry) => {
166                 let _ = entry
167                     .system_table_checked()
168                     .and_then(|v| v.boot_services_checked())
169                     .and_then(|v| v.free_pool(ptr as *mut _))
170                     .inspect_err(|e| efi_try_print!("failed to deallocate: {e}"));
171             }
172             // After EFI_BOOT_SERVICES.ExitBootServices(), all allocated memory is considered
173             // leaked and under full ownership of subsequent OS loader code.
174             _ => {}
175         }
176     }
177 }
178 
179 // Alignment guaranteed by EFI AllocatePoll()
180 const EFI_ALLOCATE_POOL_ALIGNMENT: usize = 8;
181 
182 unsafe impl GlobalAlloc for EfiAllocator {
alloc(&self, layout: Layout) -> *mut u8183     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
184         (|| -> Result<*mut u8> {
185             let align = layout.align();
186 
187             // EFI AllocatePoll() must be at 8-bytes aligned so we can just use returned pointer.
188             if align <= EFI_ALLOCATE_POOL_ALIGNMENT {
189                 let ptr = self.allocate(layout.size());
190                 assert_eq!(ptr as usize % EFI_ALLOCATE_POOL_ALIGNMENT, 0);
191                 return Ok(ptr);
192             }
193 
194             // If requested alignment is > EFI_ALLOCATE_POOL_ALIGNMENT then make sure to allocate
195             // bigger buffer and adjust ptr to be aligned.
196             let mut offset: usize = 0usize;
197             let extra_size = SafeNum::from(align) + size_of_val(&offset);
198             let size = SafeNum::from(layout.size()) + extra_size;
199 
200             // TODO(300168989):
201             // `AllocatePool()` can be slow for allocating large buffers. In this case,
202             // `AllocatePages()` is recommended.
203             let unaligned_ptr = self.allocate(size.try_into()?);
204             if unaligned_ptr.is_null() {
205                 return Err(Error::Other(Some("Allocation failed")));
206             }
207             offset = align - (unaligned_ptr as usize % align);
208 
209             // SAFETY:
210             // - `unaligned_ptr` is guaranteed to point to buffer big enough to contain offset+size
211             // bytes since this is the size passed to `allocate`
212             // - ptr+layout.size() is also pointing to valid buffer since actual allocate size takes
213             // into account additional suffix for usize variable
214             unsafe {
215                 let ptr = unaligned_ptr.add(offset);
216                 core::slice::from_raw_parts_mut(ptr.add(layout.size()), size_of_val(&offset))
217                     .copy_from_slice(&offset.to_ne_bytes());
218                 Ok(ptr)
219             }
220         })()
221         .unwrap_or(null_mut()) as _
222     }
223 
dealloc(&self, ptr: *mut u8, layout: Layout)224     unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
225         // If alignment is EFI_ALLOCATE_POOL_ALIGNMENT or less, then we can just used ptr directly
226         if layout.align() <= EFI_ALLOCATE_POOL_ALIGNMENT {
227             self.deallocate(ptr);
228             return;
229         }
230 
231         let mut offset: usize = 0usize;
232         offset = usize::from_ne_bytes(
233             // SAFETY:
234             // * `ptr` is allocated by `alloc` and has enough padding after `ptr`+size to hold
235             // suffix `offset: usize`.
236             // * Alignment of `ptr` is 1 for &[u8]
237             unsafe { core::slice::from_raw_parts(ptr.add(layout.size()), size_of_val(&offset)) }
238                 .try_into()
239                 .unwrap(),
240         );
241 
242         // SAFETY:
243         // (`ptr` - `offset`) must be valid unaligned pointer to buffer allocated by `alloc`
244         let real_start_ptr = unsafe { ptr.sub(offset) };
245         self.deallocate(real_start_ptr);
246     }
247 }
248 
249 /// API for allocating raw memory via EFI_BOOT_SERVICES
efi_malloc(size: usize) -> *mut u8250 pub fn efi_malloc(size: usize) -> *mut u8 {
251     // SAFETY: See SAFETY of `internal_efi_entry()`.
252     unsafe { EFI_GLOBAL_ALLOCATOR.allocate(size) }
253 }
254 
255 /// API for deallocating raw memory previously allocated by `efi_malloc()`. Passing invalid
256 /// pointer will cause the function to panic.
efi_free(ptr: *mut u8)257 pub fn efi_free(ptr: *mut u8) {
258     // SAFETY: See SAFETY of `internal_efi_entry()`.
259     unsafe { EFI_GLOBAL_ALLOCATOR.deallocate(ptr) }
260 }
261