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}