linux_bootloader/
efivars.rs

1use alloc::{format, string::ToString, vec, vec::Vec};
2use uefi::{
3    cstr16, guid,
4    prelude::{BootServices, RuntimeServices},
5    proto::{
6        device_path::{
7            media::{HardDrive, PartitionSignature},
8            text::DevicePathToText,
9            DevicePath, DeviceSubType, DeviceType,
10        },
11        loaded_image::LoadedImage,
12    },
13    table::{
14        runtime::{VariableAttributes, VariableVendor},
15        Boot, SystemTable,
16    },
17    CStr16, Guid, Handle, Result,
18};
19
20use bitflags::bitflags;
21
22/// Fetch the PARTUUID of a given disk
23/// FIXME(security): UEFI makes no practical guarantee about the unicity of PARTUUID
24/// This can become a problem when the threat model relies on PARTUUID unicity to
25/// detect the correct disk to unlock.
26/// See https://github.com/systemd/systemd/issues/28491 for an example.
27fn disk_get_part_uuid(boot_services: &BootServices, disk_handle: Handle) -> Result<Guid> {
28    let dp = boot_services.open_protocol_exclusive::<DevicePath>(disk_handle)?;
29
30    for node in dp.node_iter() {
31        if node.device_type() != DeviceType::MEDIA
32            || node.sub_type() != DeviceSubType::MEDIA_HARD_DRIVE
33        {
34            continue;
35        }
36
37        if let Ok(hd_path) = <&HardDrive>::try_from(node) {
38            if let PartitionSignature::Guid(guid) = hd_path.partition_signature() {
39                return Ok(guid);
40            }
41        }
42    }
43
44    Err(uefi::Status::UNSUPPORTED.into())
45}
46
47/// systemd loader's GUID
48/// != systemd's GUID
49/// https://github.com/systemd/systemd/blob/main/src/boot/efi/util.h#L114-L121
50/// https://systemd.io/BOOT_LOADER_INTERFACE/
51pub const BOOT_LOADER_VENDOR_UUID: VariableVendor =
52    VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
53
54bitflags! {
55    #[repr(transparent)]
56    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
57    /// Feature flags as described in https://systemd.io/BOOT_LOADER_INTERFACE/
58    pub struct EfiLoaderFeatures: u64 {
59       const ConfigTimeout = 1 << 0;
60       const ConfigTimeoutOneShot = 1 << 1;
61       const EntryDefault = 1 << 2;
62       const EntryOneshot = 1 << 3;
63       const BootCounting = 1 << 4;
64       const XBOOTLDR = 1 << 5;
65       const RandomSeed = 1 << 6;
66       const LoadDriver = 1 << 7;
67       const SortKey = 1 << 8;
68       const SavedEntry = 1 << 9;
69       const DeviceTree = 1 << 10;
70    }
71}
72
73/// Get the currently supported EFI features from the loader if they do exist
74/// https://systemd.io/BOOT_LOADER_INTERFACE/
75pub fn get_loader_features(runtime_services: &RuntimeServices) -> Result<EfiLoaderFeatures> {
76    if let Ok(size) =
77        runtime_services.get_variable_size(cstr16!("LoaderFeatures"), &BOOT_LOADER_VENDOR_UUID)
78    {
79        let mut buffer = vec![0; size].into_boxed_slice();
80        runtime_services.get_variable(
81            cstr16!("LoaderFeatures"),
82            &BOOT_LOADER_VENDOR_UUID,
83            &mut buffer,
84        )?;
85
86        return EfiLoaderFeatures::from_bits(u64::from_le_bytes(
87            (*buffer)
88                .try_into()
89                .map_err(|_err| uefi::Status::BAD_BUFFER_SIZE)?,
90        ))
91        .ok_or_else(|| uefi::Status::INCOMPATIBLE_VERSION.into());
92    }
93
94    Ok(Default::default())
95}
96
97bitflags! {
98    #[repr(transparent)]
99    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
100    /// Feature flags as described in https://www.freedesktop.org/software/systemd/man/systemd-stub.html
101    pub struct EfiStubFeatures: u64 {
102       /// Is `LoaderDevicePartUUID` loaded in UEFI variables?
103       const ReportBootPartition = 1 << 0;
104       /// Are credentials picked up from the boot partition?
105       const PickUpCredentials = 1 << 1;
106       /// Are system extensions picked up from the boot partition?
107       const PickUpSysExts = 1 << 2;
108       /// Are we able to measure kernel image, parameters and sysexts?
109       const ThreePcrs = 1 << 3;
110       /// Can we pass a random seed to the kernel?
111       const RandomSeed = 1 << 4;
112    }
113}
114
115// This won't work on a big endian system.
116// But okay, we do not really care, do we?
117#[cfg(target_endian = "little")]
118pub fn from_u16(from: &[u16]) -> &[u8] {
119    unsafe {
120        core::slice::from_raw_parts(from.as_ptr() as *mut u8, from.len().checked_mul(2).unwrap())
121    }
122}
123
124// Remove me when https://github.com/rust-osdev/uefi-rs/pull/788 lands
125pub fn cstr16_to_bytes(s: &CStr16) -> &[u8] {
126    from_u16(s.to_u16_slice_with_nul())
127}
128
129/// Ensures that an UEFI variable is set or set it with a fallback value
130/// computed in a lazy way.
131pub fn ensure_efi_variable<F>(
132    runtime_services: &RuntimeServices,
133    name: &CStr16,
134    vendor: &VariableVendor,
135    attributes: VariableAttributes,
136    get_fallback_value: F,
137) -> uefi::Result
138where
139    F: FnOnce() -> uefi::Result<Vec<u8>>,
140{
141    // If we get a variable size, a variable already exist.
142    if runtime_services.get_variable_size(name, vendor).is_err() {
143        runtime_services.set_variable(name, vendor, attributes, &get_fallback_value()?)?;
144    }
145
146    Ok(())
147}
148
149/// Exports systemd-stub style EFI variables
150pub fn export_efi_variables(stub_info_name: &str, system_table: &SystemTable<Boot>) -> Result<()> {
151    let boot_services = system_table.boot_services();
152    let runtime_services = system_table.runtime_services();
153
154    let stub_features: EfiStubFeatures = EfiStubFeatures::ReportBootPartition;
155
156    let loaded_image =
157        boot_services.open_protocol_exclusive::<LoadedImage>(boot_services.image_handle())?;
158
159    let default_attributes =
160        VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS;
161
162    #[allow(unused_must_use)]
163    // LoaderDevicePartUUID
164    ensure_efi_variable(
165        runtime_services,
166        cstr16!("LoaderDevicePartUUID"),
167        &BOOT_LOADER_VENDOR_UUID,
168        default_attributes,
169        || {
170            disk_get_part_uuid(boot_services, loaded_image.device()).map(|guid| {
171                guid.to_string()
172                    .encode_utf16()
173                    .flat_map(|c| c.to_le_bytes())
174                    .collect::<Vec<u8>>()
175            })
176        },
177    )
178    .ok();
179    // LoaderImageIdentifier
180    ensure_efi_variable(
181        runtime_services,
182        cstr16!("LoaderImageIdentifier"),
183        &BOOT_LOADER_VENDOR_UUID,
184        default_attributes,
185        || {
186            if let Some(dp) = loaded_image.file_path() {
187                let dp_protocol = boot_services.open_protocol_exclusive::<DevicePathToText>(
188                    boot_services.get_handle_for_protocol::<DevicePathToText>()?,
189                )?;
190                dp_protocol
191                    .convert_device_path_to_text(
192                        boot_services,
193                        dp,
194                        uefi::proto::device_path::text::DisplayOnly(false),
195                        uefi::proto::device_path::text::AllowShortcuts(false),
196                    )
197                    .map(|ps| cstr16_to_bytes(&ps).to_vec())
198            } else {
199                // If we cannot retrieve the filepath of the loaded image
200                // Then, we cannot set `LoaderImageIdentifier`.
201                Err(uefi::Status::UNSUPPORTED.into())
202            }
203        },
204    )
205    .ok();
206    // LoaderFirmwareInfo
207    ensure_efi_variable(
208        runtime_services,
209        cstr16!("LoaderFirmwareInfo"),
210        &BOOT_LOADER_VENDOR_UUID,
211        default_attributes,
212        || {
213            Ok(format!(
214                "{} {}.{:02}",
215                system_table.firmware_vendor(),
216                system_table.firmware_revision() >> 16,
217                system_table.firmware_revision() & 0xFFFFF
218            )
219            .encode_utf16()
220            .flat_map(|c| c.to_le_bytes())
221            .collect::<Vec<u8>>())
222        },
223    )
224    .ok();
225    // LoaderFirmwareType
226    ensure_efi_variable(
227        runtime_services,
228        cstr16!("LoaderFirmwareType"),
229        &BOOT_LOADER_VENDOR_UUID,
230        default_attributes,
231        || {
232            Ok(format!("UEFI {:02}", system_table.uefi_revision())
233                .encode_utf16()
234                .flat_map(|c| c.to_le_bytes())
235                .collect::<Vec<u8>>())
236        },
237    )
238    .ok();
239    // StubInfo
240    // FIXME: ideally, no one should be able to overwrite `StubInfo`, but that would require
241    // constructing an EFI authenticated variable payload. This seems overcomplicated for now.
242    runtime_services
243        .set_variable(
244            cstr16!("StubInfo"),
245            &BOOT_LOADER_VENDOR_UUID,
246            default_attributes,
247            &stub_info_name
248                .encode_utf16()
249                .flat_map(|c| c.to_le_bytes())
250                .collect::<Vec<u8>>(),
251        )
252        .ok();
253
254    // StubFeatures
255    runtime_services
256        .set_variable(
257            cstr16!("StubFeatures"),
258            &BOOT_LOADER_VENDOR_UUID,
259            default_attributes,
260            &stub_features.bits().to_le_bytes(),
261        )
262        .ok();
263
264    Ok(())
265}