222 lines
8.0 KiB
Rust
222 lines
8.0 KiB
Rust
|
use alloc::{vec::Vec, vec, string::ToString, format};
|
||
|
use uefi::{Result, Handle, prelude::{BootServices, RuntimeServices}, proto::{device_path::{DevicePath, media::{HardDrive, PartitionSignature}, DeviceType, DeviceSubType, text::DevicePathToText}, loaded_image::LoadedImage}, Guid, guid, table::{runtime::{VariableVendor, VariableAttributes}, SystemTable, Boot}, cstr16, CStr16};
|
||
|
|
||
|
use bitflags::bitflags;
|
||
|
|
||
|
fn disk_get_part_uuid(boot_services: &BootServices, disk_handle: Handle) -> Result<Guid> {
|
||
|
let dp = boot_services.open_protocol_exclusive::<DevicePath>(disk_handle)?;
|
||
|
|
||
|
for node in dp.node_iter() {
|
||
|
if node.device_type() != DeviceType::MEDIA || node.sub_type() != DeviceSubType::MEDIA_HARD_DRIVE {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if let Ok(hd_path) = <&HardDrive>::try_from(node) {
|
||
|
if let PartitionSignature::Guid(guid) = hd_path.partition_signature() {
|
||
|
return Ok(guid);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Err(uefi::Status::UNSUPPORTED.into())
|
||
|
}
|
||
|
|
||
|
|
||
|
/// systemd loader's GUID
|
||
|
/// != systemd's GUID
|
||
|
/// https://github.com/systemd/systemd/blob/main/src/boot/efi/util.h#L114-L121
|
||
|
/// https://systemd.io/BOOT_LOADER_INTERFACE/
|
||
|
pub const BOOT_LOADER_VENDOR_UUID: VariableVendor = VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
|
||
|
|
||
|
/// Lanzaboote stub name
|
||
|
pub static STUB_INFO_STRING: &str = concat!("lanzastub ", env!("CARGO_PKG_VERSION"));
|
||
|
|
||
|
bitflags! {
|
||
|
|
||
|
#[repr(transparent)]
|
||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
|
||
|
/// Feature flags as described in https://systemd.io/BOOT_LOADER_INTERFACE/
|
||
|
pub struct EfiLoaderFeatures: u64 {
|
||
|
const ConfigTimeout = 1 << 0;
|
||
|
const ConfigTimeoutOneShot = 1 << 1;
|
||
|
const EntryDefault = 1 << 2;
|
||
|
const EntryOneshot = 1 << 3;
|
||
|
const BootCounting = 1 << 4;
|
||
|
const XBOOTLDR = 1 << 5;
|
||
|
const RandomSeed = 1 << 6;
|
||
|
const LoadDriver = 1 << 7;
|
||
|
const SortKey = 1 << 8;
|
||
|
const SavedEntry = 1 << 9;
|
||
|
const DeviceTree = 1 << 10;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Get the currently supported EFI features from the loader if they do exist
|
||
|
/// https://systemd.io/BOOT_LOADER_INTERFACE/
|
||
|
pub fn get_loader_features(runtime_services: &RuntimeServices) -> Result<EfiLoaderFeatures> {
|
||
|
if let Ok(size) = runtime_services.get_variable_size(cstr16!("LoaderFeatures"), &BOOT_LOADER_VENDOR_UUID) {
|
||
|
let mut buffer = vec![0; size].into_boxed_slice();
|
||
|
runtime_services.get_variable(
|
||
|
cstr16!("LoaderFeatures"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
&mut buffer)?;
|
||
|
|
||
|
return EfiLoaderFeatures::from_bits(
|
||
|
u64::from_le_bytes(
|
||
|
(*buffer).try_into()
|
||
|
.map_err(|_err| uefi::Status::BAD_BUFFER_SIZE)?
|
||
|
))
|
||
|
.ok_or_else(|| uefi::Status::INCOMPATIBLE_VERSION.into());
|
||
|
}
|
||
|
|
||
|
Ok(Default::default())
|
||
|
}
|
||
|
|
||
|
bitflags! {
|
||
|
#[repr(transparent)]
|
||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||
|
/// Feature flags as described in https://www.freedesktop.org/software/systemd/man/systemd-stub.html
|
||
|
pub struct EfiStubFeatures: u64 {
|
||
|
/// Is `LoaderDevicePartUUID` loaded in UEFI variables?
|
||
|
const ReportBootPartition = 1 << 0;
|
||
|
/// Are credentials picked up from the boot partition?
|
||
|
const PickUpCredentials = 1 << 1;
|
||
|
/// Are system extensions picked up from the boot partition?
|
||
|
const PickUpSysExts = 1 << 2;
|
||
|
/// Are we able to measure kernel image, parameters and sysexts?
|
||
|
const ThreePcrs = 1 << 3;
|
||
|
/// Can we pass a random seed to the kernel?
|
||
|
const RandomSeed = 1 << 4;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// This won't work on a big endian system.
|
||
|
// But okay, we do not really care, do we?
|
||
|
#[cfg(target_endian = "little")]
|
||
|
pub fn from_u16(from: &[u16]) -> &[u8] {
|
||
|
unsafe {
|
||
|
core::slice::from_raw_parts(from.as_ptr() as *mut u8, from.len().checked_mul(2).unwrap())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Remove me when https://github.com/rust-osdev/uefi-rs/pull/788 lands
|
||
|
pub fn cstr16_to_bytes(s: &CStr16) -> &[u8] {
|
||
|
from_u16(s.to_u16_slice_with_nul())
|
||
|
}
|
||
|
|
||
|
/// Ensures that an UEFI variable is set or set it with a fallback value
|
||
|
/// computed in a lazy way.
|
||
|
pub fn ensure_efi_variable<F>(runtime_services: &RuntimeServices,
|
||
|
name: &CStr16,
|
||
|
vendor: &VariableVendor,
|
||
|
attributes: VariableAttributes,
|
||
|
get_fallback_value: F) -> uefi::Result
|
||
|
where F: FnOnce() -> uefi::Result<Vec<u8>>
|
||
|
{
|
||
|
// If we get a variable size, a variable already exist.
|
||
|
if runtime_services.get_variable_size(name, vendor).is_err() {
|
||
|
runtime_services.set_variable(
|
||
|
name,
|
||
|
vendor,
|
||
|
attributes,
|
||
|
&get_fallback_value()?
|
||
|
)?;
|
||
|
}
|
||
|
|
||
|
uefi::Status::SUCCESS.into()
|
||
|
}
|
||
|
|
||
|
/// Exports systemd-stub style EFI variables
|
||
|
pub fn export_efi_variables(system_table: &SystemTable<Boot>) -> Result<()> {
|
||
|
let boot_services = system_table.boot_services();
|
||
|
let runtime_services = system_table.runtime_services();
|
||
|
|
||
|
let stub_features: EfiStubFeatures =
|
||
|
EfiStubFeatures::ReportBootPartition;
|
||
|
|
||
|
let loaded_image =
|
||
|
boot_services.open_protocol_exclusive::<LoadedImage>(boot_services.image_handle())?;
|
||
|
|
||
|
let default_attributes = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS;
|
||
|
|
||
|
#[allow(unused_must_use)]
|
||
|
// LoaderDevicePartUUID
|
||
|
ensure_efi_variable(runtime_services,
|
||
|
cstr16!("LoaderDevicePartUUID"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
|| disk_get_part_uuid(boot_services, loaded_image.device()).map(|guid| guid.to_string()
|
||
|
.encode_utf16()
|
||
|
.flat_map(|c| c.to_le_bytes())
|
||
|
.collect::<Vec<u8>>()
|
||
|
)
|
||
|
).ok();
|
||
|
// LoaderImageIdentifier
|
||
|
ensure_efi_variable(runtime_services,
|
||
|
cstr16!("LoaderImageIdentifier"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
|| {
|
||
|
if let Some(dp) = loaded_image.file_path() {
|
||
|
let dp_protocol = boot_services.open_protocol_exclusive::<DevicePathToText>(
|
||
|
boot_services.get_handle_for_protocol::<DevicePathToText>()?
|
||
|
)?;
|
||
|
dp_protocol.convert_device_path_to_text(
|
||
|
boot_services,
|
||
|
dp,
|
||
|
uefi::proto::device_path::text::DisplayOnly(false),
|
||
|
uefi::proto::device_path::text::AllowShortcuts(false)
|
||
|
).map(|ps| cstr16_to_bytes(&ps).to_vec())
|
||
|
} else {
|
||
|
// If we cannot retrieve the filepath of the loaded image
|
||
|
// Then, we cannot set `LoaderImageIdentifier`.
|
||
|
Err(uefi::Status::UNSUPPORTED.into())
|
||
|
}
|
||
|
}).ok();
|
||
|
// LoaderFirmwareInfo
|
||
|
ensure_efi_variable(runtime_services,
|
||
|
cstr16!("LoaderFirmwareInfo"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
|| Ok(
|
||
|
format!("{} {}.{:02}", system_table.firmware_vendor(), system_table.firmware_revision() >> 16, system_table.firmware_revision() & 0xFFFFF)
|
||
|
.encode_utf16()
|
||
|
.flat_map(|c| c.to_le_bytes())
|
||
|
.collect::<Vec<u8>>()
|
||
|
)
|
||
|
).ok();
|
||
|
// LoaderFirmwareType
|
||
|
ensure_efi_variable(runtime_services,
|
||
|
cstr16!("LoaderFirmwareType"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
|| Ok(
|
||
|
format!("UEFI {:02}", system_table.uefi_revision())
|
||
|
.encode_utf16()
|
||
|
.flat_map(|c| c.to_le_bytes())
|
||
|
.collect::<Vec<u8>>()
|
||
|
)
|
||
|
).ok();
|
||
|
// StubInfo
|
||
|
// FIXME: ideally, no one should be able to overwrite `StubInfo`, but that would require
|
||
|
// constructing an EFI authenticated variable payload. This seems overcomplicated for now.
|
||
|
runtime_services.set_variable(
|
||
|
cstr16!("StubInfo"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
&STUB_INFO_STRING.encode_utf16().flat_map(|c| c.to_le_bytes()).collect::<Vec<u8>>()
|
||
|
).ok();
|
||
|
|
||
|
// StubFeatures
|
||
|
runtime_services.set_variable(
|
||
|
cstr16!("StubFeatures"),
|
||
|
&BOOT_LOADER_VENDOR_UUID,
|
||
|
default_attributes,
|
||
|
&stub_features.bits().to_le_bytes()
|
||
|
).ok();
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
|