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 //
68 // TODO(b/396460116): Investigate using Mutex for the variable.
69 extern "Rust" {
70 static mut EFI_GLOBAL_ALLOCATOR: EfiAllocator;
71 }
72
73 /// An internal API to obtain library internal global EfiEntry and RuntimeServices.
internal_efi_entry_and_rt( ) -> (Option<&'static EfiEntry>, Option<&'static RuntimeServices>)74 pub(crate) fn internal_efi_entry_and_rt(
75 ) -> (Option<&'static EfiEntry>, Option<&'static RuntimeServices>) {
76 // SAFETY:
77 // EFI_GLOBAL_ALLOCATOR is only read by `internal_efi_entry_and_rt()` and modified by
78 // `init_efi_global_alloc()` and `exit_efi_global_alloc()`. The safety requirements of
79 // `init_efi_global_alloc()` and `exit_efi_global_alloc()` mandate that there can be no EFI
80 // event/notification/interrupt that can be triggered when they are called. This suggests that
81 // there cannot be concurrent read and modification on `EFI_GLOBAL_ALLOCATOR` possible. Thus its
82 // access is safe from race condition.
83 #[allow(static_mut_refs)]
84 unsafe {
85 EFI_GLOBAL_ALLOCATOR.get_efi_entry_and_rt()
86 }
87 }
88
89 /// Try to print via `EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL` in `EFI_SYSTEM_TABLE.ConOut`.
90 ///
91 /// Errors are ignored.
92 #[macro_export]
93 macro_rules! efi_try_print {
94 ($( $x:expr ),* $(,)? ) => {
95 {
96 let _ = (|| -> Result<()> {
97 if let Some(entry) = crate::allocation::internal_efi_entry_and_rt().0 {
98 write!(entry.system_table_checked()?.con_out()?, $($x,)*)?;
99 }
100 Ok(())
101 })();
102 }
103 };
104 }
105
106 /// Initializes global allocator.
107 ///
108 /// # Safety
109 ///
110 /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when
111 /// there is no event/notification function that can be triggered or modify it. Otherwise there
112 /// is a risk of race condition.
init_efi_global_alloc(efi_entry: EfiEntry) -> Result<()>113 pub(crate) unsafe fn init_efi_global_alloc(efi_entry: EfiEntry) -> Result<()> {
114 // SAFETY: See SAFETY of `internal_efi_entry_and_rt()`
115 unsafe {
116 EFI_GLOBAL_ALLOCATOR.runtime_services =
117 efi_entry.system_table_checked().and_then(|v| v.runtime_services_checked()).ok();
118 match EFI_GLOBAL_ALLOCATOR.state {
119 EfiState::Uninitialized => {
120 EFI_GLOBAL_ALLOCATOR.state = EfiState::Initialized(efi_entry);
121 Ok(())
122 }
123 _ => Err(Error::AlreadyStarted),
124 }
125 }
126 }
127
128 /// Internal API to invalidate global allocator after ExitBootService().
129 ///
130 /// # Safety
131 ///
132 /// This function modifies global variable `EFI_GLOBAL_ALLOCATOR`. It should only be called when
133 /// there is no event/notification function that can be triggered or modify it. Otherwise there
134 /// is a risk of race condition.
exit_efi_global_alloc()135 pub(crate) unsafe fn exit_efi_global_alloc() {
136 // SAFETY: See SAFETY of `internal_efi_entry_and_rt()`
137 unsafe {
138 EFI_GLOBAL_ALLOCATOR.state = EfiState::Exited;
139 }
140 }
141
142 impl EfiAllocator {
143 /// Creates a new instance.
new() -> Self144 pub const fn new() -> Self {
145 Self { state: EfiState::Uninitialized, runtime_services: None }
146 }
147
148 /// Gets EfiEntry and RuntimeServices
get_efi_entry_and_rt(&self) -> (Option<&EfiEntry>, Option<&RuntimeServices>)149 fn get_efi_entry_and_rt(&self) -> (Option<&EfiEntry>, Option<&RuntimeServices>) {
150 (self.state.efi_entry(), self.runtime_services.as_ref())
151 }
152
153 /// Allocate memory via EFI_BOOT_SERVICES.
allocate(&self, size: usize) -> *mut u8154 fn allocate(&self, size: usize) -> *mut u8 {
155 self.state
156 .efi_entry()
157 .ok_or(Error::InvalidState)
158 .and_then(|v| v.system_table_checked())
159 .and_then(|v| v.boot_services_checked())
160 .and_then(|v| v.allocate_pool(EFI_MEMORY_TYPE_LOADER_DATA, size))
161 .inspect_err(|e| efi_try_print!("failed to allocate: {e}"))
162 .unwrap_or(null_mut()) as _
163 }
164
165 /// Deallocate memory previously allocated by `Self::allocate()`.
166 ///
167 /// Errors are logged but ignored.
deallocate(&self, ptr: *mut u8)168 fn deallocate(&self, ptr: *mut u8) {
169 match self.state.efi_entry() {
170 Some(ref entry) => {
171 let _ = entry
172 .system_table_checked()
173 .and_then(|v| v.boot_services_checked())
174 .and_then(|v| v.free_pool(ptr as *mut _))
175 .inspect_err(|e| efi_try_print!("failed to deallocate: {e}"));
176 }
177 // After EFI_BOOT_SERVICES.ExitBootServices(), all allocated memory is considered
178 // leaked and under full ownership of subsequent OS loader code.
179 _ => {}
180 }
181 }
182 }
183
184 // Alignment guaranteed by EFI AllocatePoll()
185 const EFI_ALLOCATE_POOL_ALIGNMENT: usize = 8;
186
187 unsafe impl GlobalAlloc for EfiAllocator {
alloc(&self, layout: Layout) -> *mut u8188 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
189 (|| -> Result<*mut u8> {
190 let align = layout.align();
191
192 // EFI AllocatePoll() must be at 8-bytes aligned so we can just use returned pointer.
193 if align <= EFI_ALLOCATE_POOL_ALIGNMENT {
194 let ptr = self.allocate(layout.size());
195 assert_eq!(ptr as usize % EFI_ALLOCATE_POOL_ALIGNMENT, 0);
196 return Ok(ptr);
197 }
198
199 // If requested alignment is > EFI_ALLOCATE_POOL_ALIGNMENT then make sure to allocate
200 // bigger buffer and adjust ptr to be aligned.
201 let mut offset: usize = 0usize;
202 let extra_size = SafeNum::from(align) + size_of_val(&offset);
203 let size = SafeNum::from(layout.size()) + extra_size;
204
205 // TODO(300168989):
206 // `AllocatePool()` can be slow for allocating large buffers. In this case,
207 // `AllocatePages()` is recommended.
208 let unaligned_ptr = self.allocate(size.try_into()?);
209 if unaligned_ptr.is_null() {
210 return Err(Error::Other(Some("Allocation failed")));
211 }
212 offset = align - (unaligned_ptr as usize % align);
213
214 // SAFETY:
215 // - `unaligned_ptr` is guaranteed to point to buffer big enough to contain offset+size
216 // bytes since this is the size passed to `allocate`
217 // - ptr+layout.size() is also pointing to valid buffer since actual allocate size takes
218 // into account additional suffix for usize variable
219 unsafe {
220 let ptr = unaligned_ptr.add(offset);
221 core::slice::from_raw_parts_mut(ptr.add(layout.size()), size_of_val(&offset))
222 .copy_from_slice(&offset.to_ne_bytes());
223 Ok(ptr)
224 }
225 })()
226 .unwrap_or(null_mut()) as _
227 }
228
dealloc(&self, ptr: *mut u8, layout: Layout)229 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
230 // If alignment is EFI_ALLOCATE_POOL_ALIGNMENT or less, then we can just used ptr directly
231 if layout.align() <= EFI_ALLOCATE_POOL_ALIGNMENT {
232 self.deallocate(ptr);
233 return;
234 }
235
236 let mut offset: usize = 0usize;
237 offset = usize::from_ne_bytes(
238 // SAFETY:
239 // * `ptr` is allocated by `alloc` and has enough padding after `ptr`+size to hold
240 // suffix `offset: usize`.
241 // * Alignment of `ptr` is 1 for &[u8]
242 unsafe { core::slice::from_raw_parts(ptr.add(layout.size()), size_of_val(&offset)) }
243 .try_into()
244 .unwrap(),
245 );
246
247 // SAFETY:
248 // (`ptr` - `offset`) must be valid unaligned pointer to buffer allocated by `alloc`
249 let real_start_ptr = unsafe { ptr.sub(offset) };
250 self.deallocate(real_start_ptr);
251 }
252 }
253