• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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