bootmgr_rs_core/boot/loader/efi.rs
1// SPDX-FileCopyrightText: 2025 some100 <ootinnyoo@outlook.com>
2// SPDX-License-Identifier: MIT
3
4//! The boot loader for EFI executables
5//!
6//! This will also handle devicetree installs and Shim authentication if either are available.
7//!
8//! # Safety
9//!
10//! This uses unsafe in one place that is completely safe.
11//!
12//! 1. The `set_load_options` method requires unsafe to call, as it requires one condition that is upheld by the
13//! program. This one condition is that `ptr` must not be freed, or that it lasts long enough. This is ensured
14//! by the usage of a static [`RefCell`], so this is safe.
15
16use core::cell::RefCell;
17
18use crate::{
19 BootResult,
20 boot::{
21 devicetree::install_devicetree,
22 loader::{LoadError, get_efi},
23 secure_boot::shim::shim_load_image,
24 },
25 config::Config,
26 system::{
27 fs::UefiFileSystem,
28 helper::{join_to_device_path, str_to_cstr},
29 },
30};
31
32use uefi::{
33 CStr16, CString16, Handle,
34 boot::{self, ScopedProtocol},
35 proto::{device_path::DevicePath, loaded_image::LoadedImage},
36};
37
38/// An instance of `LoadOptions` that remains for the lifetime of the program.
39/// This is because load options must last long enough so that it can be safely
40/// passed into [`LoadOptions::set_load_options`].
41///
42/// A [`RefCell`] is used here as `LoadOptions` may need to be modified more than once.
43/// This is the case if the `shim_load_image` function fails.
44static LOAD_OPTIONS: LoadOptions = LoadOptions {
45 options: RefCell::new(None),
46};
47
48/// Storage struct for a [`CString16`] with load options.
49struct LoadOptions {
50 /// [`RefCell`] wrapper around the load options.
51 options: RefCell<Option<CString16>>,
52}
53
54impl LoadOptions {
55 /// Set the current load options from a [`CStr16`] slice.
56 fn set(&self, s: &CStr16) {
57 let mut options = self.options.borrow_mut();
58 *options = Some(s.into());
59 }
60
61 /// Get the current load options as a possibly null u8 raw pointer.
62 fn get(&self) -> Option<*const u8> {
63 self.options
64 .borrow()
65 .as_ref()
66 .map(|x| x.as_ptr().cast::<u8>())
67 }
68
69 /// Get the number of bytes of the load options.
70 fn size(&self) -> usize {
71 self.options.borrow().as_ref().map_or(0, |x| x.num_bytes())
72 }
73
74 /// Set the load options of an image to the load options of the struct.
75 fn set_load_options(&self, image: &mut ScopedProtocol<LoadedImage>) {
76 if let Some(ptr) = self.get() {
77 // it is quite unlikely that the load options will literally exceed 4 gb in length, so its safe to truncate
78 let size = u32::try_from(self.size()).unwrap_or(0);
79 // SAFETY: this should ONLY be used with a static cell, as the pointer must last long enough for the loaded image to use it
80 unsafe {
81 image.set_load_options(ptr, size);
82 }
83 }
84 }
85}
86
87// SAFETY: uefi is a single threaded environment, thread safety is irrelevant
88unsafe impl Sync for LoadOptions {}
89
90/// Loads a boot option from a given [`Config`] through EFI.
91///
92/// This function loads an EFI executable defined in config.efi, and optionally
93/// may also install devicetree for ARM devices, and can set load options in
94/// config.options.
95///
96/// # Errors
97///
98/// May return an `Error` for many reasons, see [`boot::load_image`] and [`boot::open_protocol_exclusive`]
99pub(crate) fn load_boot_option(config: &Config) -> BootResult<Handle> {
100 let handle = *config
101 .fs_handle
102 .ok_or_else(|| LoadError::ConfigMissingHandle(config.filename.clone()))?;
103
104 let mut fs = UefiFileSystem::from_handle(handle)?;
105
106 let file = get_efi(config)?;
107
108 let s = str_to_cstr(file)?;
109
110 let handle = load_image_from_path(handle, &s)?;
111
112 setup_image(&mut fs, handle, config)
113}
114
115/// Load an image given a [`Handle`] and a path.
116///
117/// # Errors
118///
119/// May return an `Error` if the handle does not support [`DevicePath`], or the image could not be loaded.
120fn load_image_from_path(handle: Handle, path: &CStr16) -> BootResult<Handle> {
121 let dev_path = boot::open_protocol_exclusive::<DevicePath>(handle)?;
122 let mut buf = [0; 2048]; // it should be rare for a devicepath to exceed 2048 bytes
123 let path = join_to_device_path(&dev_path, path, &mut buf)?;
124
125 let src = boot::LoadImageSource::FromDevicePath {
126 device_path: &path,
127 boot_policy: uefi::proto::BootPolicy::BootSelection,
128 };
129 shim_load_image(boot::image_handle(), src) // this will either load with shim validation, or just load the image
130}
131
132/// Sets up the image for boot with load options and optionally loading a devicetree.
133///
134/// # Errors
135///
136/// May return an `Error` if the image does not support [`LoadedImage`], or, if a devicetree
137/// is present, the devicetree could not be installed.
138fn setup_image(fs: &mut UefiFileSystem, handle: Handle, config: &Config) -> BootResult<Handle> {
139 if let Some(devicetree) = &config.devicetree_path {
140 install_devicetree(devicetree, fs)?;
141 }
142
143 let mut image = boot::open_protocol_exclusive::<LoadedImage>(handle)?;
144
145 if let Some(options) = config.options.as_deref() {
146 let load_options = &LOAD_OPTIONS;
147
148 load_options.set(&str_to_cstr(options)?);
149
150 load_options.set_load_options(&mut image);
151 }
152
153 Ok(handle)
154}