use alloc::{format, string::ToString, vec::Vec};
use bitflags::bitflags;
use sha2::{Digest, Sha256};
use uefi::{
CStr16, boot, cstr16,
data_types::EqStrUntilNul,
guid,
proto::rng::Rng,
runtime::{self, VariableVendor},
};
use crate::{
BootResult,
config::Config,
system::{
fs::{UefiFileSystem, get_partition_guid},
helper::{locate_protocol, str_to_cstr},
time::timer_usec,
variable::{get_variable_str, set_variable, set_variable_str, set_variable_u16_slice},
},
};
const BLI_VENDOR: VariableVendor = VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
const RANDOM_SEED_PATH: &CStr16 = cstr16!("\\loader\\random-seed");
bitflags! {
struct LoaderFeatures: u64 {
const TIMEOUT = 1 << 0;
const TIMEOUT_ONESHOT = 1 << 1;
const ENTRY_DEFAULT = 1 << 2;
const ENTRY_ONESHOT = 1 << 3;
const BOOT_COUNTER = 1 << 4;
const XBOOTLDR = 1 << 5;
const RANDOM_SEED = 1 << 6;
const LOAD_DRIVER = 1 << 7;
const SORT_KEY = 1 << 8;
const SAVED_ENTRY = 1 << 9;
const DEVICETREE = 1 << 10;
const SECUREBOOT_ENROLL = 1 << 11;
const RETAIN_SHIM = 1 << 12;
const MENU_DISABLED = 1 << 13;
const MULTI_PROFILE_UKI = 1 << 14;
const REPORT_URL = 1 << 15;
const TYPE1_UKI = 1 << 16;
const TYPE1_UKI_URL = 1 << 17;
const TPM2_ACTIVE_PCR_BANKS = 1 << 18;
}
}
pub(crate) fn export_variables() -> BootResult<()> {
let supported = LoaderFeatures::TIMEOUT
| LoaderFeatures::TIMEOUT_ONESHOT
| LoaderFeatures::ENTRY_DEFAULT
| LoaderFeatures::ENTRY_ONESHOT
| LoaderFeatures::BOOT_COUNTER
| LoaderFeatures::XBOOTLDR
| LoaderFeatures::RANDOM_SEED
| LoaderFeatures::SORT_KEY
| LoaderFeatures::DEVICETREE
| LoaderFeatures::RETAIN_SHIM
| LoaderFeatures::MENU_DISABLED;
let time = str_to_cstr(&timer_usec().to_string())?;
let partition_guid =
get_partition_guid(boot::image_handle()).and_then(|x| str_to_cstr(&x.to_string()).ok());
let info = str_to_cstr(&format!("bootmgr-rs {}", env!("CARGO_PKG_VERSION")))?;
set_variable_str(
cstr16!("LoaderTimeInitUSec"),
Some(BLI_VENDOR),
None,
Some(&time),
)?;
set_variable(
cstr16!("LoaderFeatures"),
Some(BLI_VENDOR),
None,
Some(supported.bits()),
)?;
set_variable_str(
cstr16!("LoaderDevicePartUUID"),
Some(BLI_VENDOR),
None,
partition_guid.as_deref(),
)?;
set_variable_str(cstr16!("LoaderInfo"), Some(BLI_VENDOR), None, Some(&info))?;
Ok(())
}
pub(crate) fn record_exit_time() -> BootResult<()> {
let time = str_to_cstr(&timer_usec().to_string())?;
set_variable_str(
cstr16!("LoaderTimeExecUSec"),
Some(BLI_VENDOR),
None,
Some(&time),
)?;
Ok(())
}
pub(crate) fn set_loader_entries(configs: &[Config]) -> BootResult<()> {
let filenames: Vec<_> = configs
.iter()
.flat_map(|x: &Config| str_to_cstr(&x.filename))
.collect();
let entries: Vec<_> = filenames
.iter()
.map(|x| x.to_u16_slice_with_nul())
.flat_map(|x| x.iter().copied())
.collect();
set_variable_u16_slice(
cstr16!("LoaderEntries"),
Some(BLI_VENDOR),
None,
Some(&entries),
)
}
pub(crate) fn get_default_entry(configs: &[Config]) -> Option<usize> {
let default = get_variable_str(cstr16!("LoaderEntryDefault"), Some(BLI_VENDOR)).ok();
let oneshot = get_variable_str(cstr16!("LoaderEntryOneShot"), Some(BLI_VENDOR)).ok();
oneshot.map_or_else(
|| {
default.and_then(|default| {
configs
.iter()
.position(|x| x.filename.eq_str_until_nul(&default))
})
},
|oneshot| {
configs
.iter()
.position(|x| x.filename.eq_str_until_nul(&oneshot))
},
)
}
pub(crate) fn set_default_entry(configs: &[Config], idx: usize) -> BootResult<()> {
let timeout = str_to_cstr(&configs[idx].filename)?;
set_variable_str(
cstr16!("LoaderEntryDefault"),
Some(BLI_VENDOR),
None,
Some(&timeout),
)
}
#[allow(dead_code)]
pub(crate) fn get_timeout_var() -> Option<i64> {
let timeout = get_variable_str(cstr16!("LoaderConfigTimeout"), Some(BLI_VENDOR)).ok();
let oneshot = get_variable_str(cstr16!("LoaderConfigTimeoutOneshot"), Some(BLI_VENDOR)).ok();
oneshot.map_or_else(
|| timeout.and_then(|timeout| match_timeout(&timeout)),
|oneshot| {
let _ = set_variable_str(
cstr16!("LoaderConfigTimeoutOneshot"),
Some(BLI_VENDOR),
None,
None,
);
match_timeout(&oneshot)
},
)
}
#[allow(dead_code)]
pub(crate) fn set_timeout_var(timeout: i64) -> BootResult<()> {
let timeout = str_to_cstr(&timeout.to_string())?;
set_variable_str(
cstr16!("LoaderConfigTimeout"),
Some(BLI_VENDOR),
None,
Some(&timeout),
)
}
fn match_timeout(timeout: &uefi::CStr16) -> Option<i64> {
if timeout.eq_str_until_nul("menu-force") {
Some(-1)
} else if timeout.eq_str_until_nul("menu-hidden") || timeout.eq_str_until_nul("menu-disabled") {
Some(0)
} else {
timeout.to_string().parse().ok()
}
}
pub(crate) fn generate_random_seed() -> BootResult<()> {
let mut fs = UefiFileSystem::from_image_fs()?;
let mut hasher = Sha256::new();
if let Ok(content) = fs.read(RANDOM_SEED_PATH) {
hasher.update(&content);
}
if let Ok((token, _)) = runtime::get_variable_boxed(cstr16!("LoaderSystemToken"), &BLI_VENDOR) {
hasher.update(&token);
}
if let Ok(mut rng) = locate_protocol::<Rng>() {
let mut buf = [0; 64];
let _ = rng.get_rng(None, &mut buf);
hasher.update(buf);
}
hasher.update(timer_usec().to_le_bytes());
let result = hasher.finalize();
let _ = fs.delete(RANDOM_SEED_PATH);
fs.create(RANDOM_SEED_PATH)?;
fs.write(RANDOM_SEED_PATH, &result)?;
Ok(())
}