use crate::integrations::bootloader_interface::bitflags::LoaderFeatures; use crate::platform::timer::PlatformTimer; use crate::utils::device_path_subpath; use crate::utils::variables::{VariableClass, VariableController}; use anyhow::{Context, Result}; use uefi::proto::device_path::DevicePath; use uefi::{Guid, guid}; use uefi_raw::table::runtime::VariableVendor; /// bitflags: LoaderFeatures bitflags. mod bitflags; /// The name of the bootloader to tell the system. const LOADER_NAME: &str = "Sprout"; /// Represents the configured timeout for the bootloader interface. pub enum BootloaderInterfaceTimeout { /// Force the menu to be shown. MenuForce, /// Hide the menu. MenuHidden, /// Disable the menu. MenuDisabled, /// Set a timeout for the menu. Timeout(u64), /// Timeout is unspecified. Unspecified, } /// Bootloader Interface support. pub struct BootloaderInterface; impl BootloaderInterface { /// Bootloader Interface GUID from https://systemd.io/BOOT_LOADER_INTERFACE const VENDOR: VariableController = VariableController::new(VariableVendor(guid!( "4a67b082-0a4c-41cf-b6c7-440b29bb8c4f" ))); /// The feature we support in Sprout. fn features() -> LoaderFeatures { LoaderFeatures::Xbootldr | LoaderFeatures::LoadDriver | LoaderFeatures::Tpm2ActivePcrBanks | LoaderFeatures::RetainShim | LoaderFeatures::ConfigTimeout | LoaderFeatures::ConfigTimeoutOneShot | LoaderFeatures::MenuDisable } /// Tell the system that Sprout was initialized at the current time. pub fn mark_init(timer: &PlatformTimer) -> Result<()> { Self::mark_time("LoaderTimeInitUSec", timer) } /// Tell the system that Sprout is about to execute the boot entry. pub fn mark_exec(timer: &PlatformTimer) -> Result<()> { Self::mark_time("LoaderTimeExecUSec", timer) } /// Tell the system that Sprout is about to display the menu. pub fn mark_menu(timer: &PlatformTimer) -> Result<()> { Self::mark_time("LoaderTimeMenuUsec", timer) } /// Tell the system about the current time as measured by the platform timer. /// Sets the variable specified by `key` to the number of microseconds. fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> { // Measure the elapsed time since the hardware timer was started. let elapsed = timer.elapsed_since_lifetime(); Self::VENDOR.set_cstr16( key, &elapsed.as_micros().to_string(), VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what loader is being used and our features. pub fn set_loader_info() -> Result<()> { // Set the LoaderInfo variable with the name of the loader. Self::VENDOR .set_cstr16( "LoaderInfo", LOADER_NAME, VariableClass::BootAndRuntimeTemporary, ) .context("unable to set loader info variable")?; // Set the LoaderFeatures variable with the features we support. Self::VENDOR .set_u64le( "LoaderFeatures", Self::features().bits(), VariableClass::BootAndRuntimeTemporary, ) .context("unable to set loader features variable")?; Ok(()) } /// Tell the system the relative path to the partition root of the current bootloader. pub fn set_loader_path(path: &DevicePath) -> Result<()> { let subpath = device_path_subpath(path).context("unable to get loader path subpath")?; Self::VENDOR.set_cstr16( "LoaderImageIdentifier", &subpath, VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what the partition GUID of the ESP Sprout was booted from is. pub fn set_partition_guid(guid: &Guid) -> Result<()> { Self::VENDOR.set_cstr16( "LoaderDevicePartUUID", &guid.to_string(), VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what boot entries are available. pub fn set_entries>(entries: impl Iterator) -> Result<()> { // Entries are stored as a null-terminated list of CString16 strings back to back. // Iterate over the entries and convert them to CString16 placing them into data. let mut data = Vec::new(); for entry in entries { // Convert the entry to CString16 little endian. let encoded = entry .as_ref() .encode_utf16() .flat_map(|c| c.to_le_bytes()) .collect::>(); // Write the bytes into the data buffer. data.extend_from_slice(&encoded); // Add a null terminator to the end of the entry. data.extend_from_slice(&[0, 0]); } Self::VENDOR.set( "LoaderEntries", &data, VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what the default boot entry is. pub fn set_default_entry(entry: String) -> Result<()> { Self::VENDOR.set_cstr16( "LoaderEntryDefault", &entry, VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what the selected boot entry is. pub fn set_selected_entry(entry: String) -> Result<()> { Self::VENDOR.set_cstr16( "LoaderEntrySelected", &entry, VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system about the UEFI firmware we are running on. pub fn set_firmware_info() -> Result<()> { // Access the firmware revision. let firmware_revision = uefi::system::firmware_revision(); // Access the UEFI revision. let uefi_revision = uefi::system::uefi_revision(); // Format the firmware information string into something human-readable. let firmware_info = format!( "{} {}.{:02}", uefi::system::firmware_vendor(), firmware_revision >> 16, firmware_revision & 0xffff, ); Self::VENDOR.set_cstr16( "LoaderFirmwareInfo", &firmware_info, VariableClass::BootAndRuntimeTemporary, )?; // Format the firmware revision into something human-readable. let firmware_type = format!( "UEFI {}.{:02}", uefi_revision.major(), uefi_revision.minor() ); Self::VENDOR.set_cstr16( "LoaderFirmwareType", &firmware_type, VariableClass::BootAndRuntimeTemporary, ) } /// Tell the system what the number of active PCR banks is. /// If this is zero, that is okay. pub fn set_tpm2_active_pcr_banks(value: u32) -> Result<()> { // Format the value into the specification format. let value = format!("0x{:08x}", value); Self::VENDOR.set_cstr16( "LoaderTpm2ActivePcrBanks", &value, VariableClass::BootAndRuntimeTemporary, ) } /// Retrieve the timeout value from the bootloader interface, using the specified `key`. /// `remove` indicates whether, when found, we remove the variable. fn get_timeout_value(key: &str, remove: bool) -> Result> { // Retrieve the timeout value from the bootloader interface. let Some(value) = Self::VENDOR .get_cstr16(key) .context("unable to get timeout value")? else { return Ok(None); }; // If we reach here, we know the value was specified. // If `remove` is true, remove the variable. if remove { Self::VENDOR .remove(key) .context("unable to remove timeout variable")?; } // If the value is empty, return Unspecified. if value.is_empty() { return Ok(Some(BootloaderInterfaceTimeout::Unspecified)); } // If the value is "menu-force", return MenuForce. if value == "menu-force" { return Ok(Some(BootloaderInterfaceTimeout::MenuForce)); } // If the value is "menu-hidden", return MenuHidden. if value == "menu-hidden" { return Ok(Some(BootloaderInterfaceTimeout::MenuHidden)); } // If the value is "menu-disabled", return MenuDisabled. if value == "menu-disabled" { return Ok(Some(BootloaderInterfaceTimeout::MenuDisabled)); } // Parse the value as a u64 to decode an numeric value. let value = value .parse::() .context("unable to parse timeout value")?; // The specification says that a value of 0 means that the menu should be hidden. if value == 0 { return Ok(Some(BootloaderInterfaceTimeout::MenuHidden)); } // If we reach here, we know it must be a real timeout value. Ok(Some(BootloaderInterfaceTimeout::Timeout(value))) } /// Get the timeout from the bootloader interface. /// This indicates how the menu should behave. /// If no values are set, Unspecified is returned. pub fn get_timeout() -> Result { // Attempt to acquire the value of the LoaderConfigTimeoutOneShot variable. // This should take precedence over the LoaderConfigTimeout variable. let oneshot = Self::get_timeout_value("LoaderConfigTimeoutOneShot", true) .context("unable to check for LoaderConfigTimeoutOneShot variable")?; // If oneshot was found, return it. if let Some(oneshot) = oneshot { return Ok(oneshot); } // Attempt to acquire the value of the LoaderConfigTimeout variable. // This will be used if the LoaderConfigTimeoutOneShot variable is not set. let direct = Self::get_timeout_value("LoaderConfigTimeout", false) .context("unable to check for LoaderConfigTimeout variable")?; // If direct was found, return it. if let Some(direct) = direct { return Ok(direct); } // If we reach here, we know that neither variable was set. // We provide the unspecified value instead. Ok(BootloaderInterfaceTimeout::Unspecified) } }