1//! `LoadedImage` protocol.
2
3use crate::{
4 data_types::FromSliceWithNulError,
5 proto::device_path::{DevicePath, FfiDevicePath},
6 proto::unsafe_protocol,
7 table::boot::MemoryType,
8 util::usize_from_u32,
9 CStr16, Handle, Status,
10};
11use core::{ffi::c_void, mem, slice};
12
13/// The LoadedImage protocol. This can be opened on any image handle using the `HandleProtocol` boot service.
14#[repr(C)]
15#[unsafe_protocol("5b1b31a1-9562-11d2-8e3f-00a0c969723b")]
16pub struct LoadedImage {
17 revision: u32,
18 parent_handle: Handle,
19 system_table: *const c_void,
20
21 // Source location of the image
22 device_handle: Handle,
23 file_path: *const FfiDevicePath,
24 _reserved: *const c_void,
25
26 // Image load options
27 load_options_size: u32,
28 load_options: *const u8,
29
30 // Location where image was loaded
31 image_base: *const c_void,
32 image_size: u64,
33 image_code_type: MemoryType,
34 image_data_type: MemoryType,
35 /// This is a callback that a loaded image can use to do cleanup. It is called by the
36 /// `UnloadImage` boot service.
37 unload: extern "efiapi" fn(image_handle: Handle) -> Status,
38}
39
40/// Errors that can be raised during parsing of the load options.
41#[derive(Debug)]
42pub enum LoadOptionsError {
43 /// Load options are not set.
44 NotSet,
45
46 /// The start and/or length of the load options is not [`u16`]-aligned.
47 NotAligned,
48
49 /// Not a valid null-terminated UCS-2 string.
50 InvalidString(FromSliceWithNulError),
51}
52
53impl LoadedImage {
54 /// Returns a handle to the storage device on which the image is located.
55 #[must_use]
56 pub const fn device(&self) -> Handle {
57 self.device_handle
58 }
59
60 /// Get a reference to the `file_path`.
61 ///
62 /// Return `None` if the pointer to the file path portion specific to
63 /// DeviceHandle that the EFI Image was loaded from is null.
64 #[must_use]
65 pub fn file_path(&self) -> Option<&DevicePath> {
66 if self.file_path.is_null() {
67 None
68 } else {
69 unsafe { Some(DevicePath::from_ffi_ptr(self.file_path)) }
70 }
71 }
72
73 /// Get the load options of the image as a [`&CStr16`].
74 ///
75 /// Load options are typically used to pass command-line options as
76 /// a null-terminated UCS-2 string. This format is not required
77 /// though; use [`load_options_as_bytes`] to access the raw bytes.
78 ///
79 /// [`&CStr16`]: `CStr16`
80 /// [`load_options_as_bytes`]: `Self::load_options_as_bytes`
81 pub fn load_options_as_cstr16(&self) -> Result<&CStr16, LoadOptionsError> {
82 let load_options_size = usize_from_u32(self.load_options_size);
83
84 if self.load_options.is_null() {
85 Err(LoadOptionsError::NotSet)
86 } else if (load_options_size % mem::size_of::<u16>() != 0)
87 || (((self.load_options as usize) % mem::align_of::<u16>()) != 0)
88 {
89 Err(LoadOptionsError::NotAligned)
90 } else {
91 let s = unsafe {
92 slice::from_raw_parts(
93 self.load_options.cast::<u16>(),
94 load_options_size / mem::size_of::<u16>(),
95 )
96 };
97 CStr16::from_u16_with_nul(s).map_err(LoadOptionsError::InvalidString)
98 }
99 }
100
101 /// Get the load options of the image as raw bytes.
102 ///
103 /// UEFI allows arbitrary binary data in load options, but typically
104 /// the data is a null-terminated UCS-2 string. Use
105 /// [`load_options_as_cstr16`] to more conveniently access the load
106 /// options as a string.
107 ///
108 /// Returns `None` if load options are not set.
109 ///
110 /// [`load_options_as_cstr16`]: `Self::load_options_as_cstr16`
111 #[must_use]
112 pub fn load_options_as_bytes(&self) -> Option<&[u8]> {
113 if self.load_options.is_null() {
114 None
115 } else {
116 unsafe {
117 Some(slice::from_raw_parts(
118 self.load_options,
119 usize_from_u32(self.load_options_size),
120 ))
121 }
122 }
123 }
124
125 /// Set the image data address and size.
126 ///
127 /// This is useful in the following scenario:
128 /// 1. Secure boot is enabled, so images loaded with `LoadImage` must be
129 /// signed with an appropriate key known to the firmware.
130 /// 2. The bootloader has its own key embedded, and uses that key to
131 /// verify the next stage. This key is not known to the firmware, so
132 /// the next stage's image can't be loaded with `LoadImage`.
133 /// 3. Since image handles are created by `LoadImage`, which we can't
134 /// call, we have to make use of an existing image handle -- the one
135 /// passed into the bootloader's entry function. By modifying that
136 /// image handle (after appropriately verifying the signature of the
137 /// new data), we can repurpose the image handle for the next stage.
138 ///
139 /// See [shim] for an example of this scenario.
140 ///
141 /// # Safety
142 ///
143 /// This function takes `data` as a raw pointer because the data is not
144 /// owned by `LoadedImage`. The caller must ensure that the memory lives
145 /// long enough.
146 ///
147 /// [shim]: https://github.com/rhboot/shim/blob/4d64389c6c941d21548b06423b8131c872e3c3c7/pe.c#L1143
148 pub unsafe fn set_image(&mut self, data: *const c_void, size: u64) {
149 self.image_base = data;
150 self.image_size = size;
151 }
152
153 /// Set the load options for the image. This can be used prior to
154 /// calling `BootServices.start_image` to control the command line
155 /// passed to the image.
156 ///
157 /// `size` is in bytes.
158 ///
159 /// # Safety
160 ///
161 /// This function takes `options` as a raw pointer because the
162 /// load options data is not owned by `LoadedImage`. The caller
163 /// must ensure that the memory lives long enough.
164 pub unsafe fn set_load_options(&mut self, options: *const u8, size: u32) {
165 self.load_options = options;
166 self.load_options_size = size;
167 }
168
169 /// Returns the base address and the size in bytes of the loaded image.
170 #[must_use]
171 pub const fn info(&self) -> (*const c_void, u64) {
172 (self.image_base, self.image_size)
173 }
174}
175