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