mirror of
https://github.com/edera-dev/sprout.git
synced 2025-12-19 13:50:16 +00:00
feat(safety): bail if secure boot is enabled early
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
use crate::platform::timer::PlatformTimer;
|
use crate::platform::timer::PlatformTimer;
|
||||||
use crate::utils::device_path_subpath;
|
use crate::utils::device_path_subpath;
|
||||||
|
use crate::utils::variables::{VariableClass, VariableController};
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use uefi::proto::device_path::DevicePath;
|
use uefi::proto::device_path::DevicePath;
|
||||||
use uefi::{CString16, Guid, guid};
|
use uefi::{Guid, guid};
|
||||||
use uefi_raw::table::runtime::{VariableAttributes, VariableVendor};
|
use uefi_raw::table::runtime::VariableVendor;
|
||||||
|
|
||||||
/// The name of the bootloader to tell the system.
|
/// The name of the bootloader to tell the system.
|
||||||
const LOADER_NAME: &str = "Sprout";
|
const LOADER_NAME: &str = "Sprout";
|
||||||
@@ -13,7 +14,9 @@ pub struct BootloaderInterface;
|
|||||||
|
|
||||||
impl BootloaderInterface {
|
impl BootloaderInterface {
|
||||||
/// Bootloader Interface GUID from https://systemd.io/BOOT_LOADER_INTERFACE
|
/// Bootloader Interface GUID from https://systemd.io/BOOT_LOADER_INTERFACE
|
||||||
const VENDOR: VariableVendor = VariableVendor(guid!("4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"));
|
const VENDOR: VariableController = VariableController::new(VariableVendor(guid!(
|
||||||
|
"4a67b082-0a4c-41cf-b6c7-440b29bb8c4f"
|
||||||
|
)));
|
||||||
|
|
||||||
/// Tell the system that Sprout was initialized at the current time.
|
/// Tell the system that Sprout was initialized at the current time.
|
||||||
pub fn mark_init(timer: &PlatformTimer) -> Result<()> {
|
pub fn mark_init(timer: &PlatformTimer) -> Result<()> {
|
||||||
@@ -35,23 +38,39 @@ impl BootloaderInterface {
|
|||||||
fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> {
|
fn mark_time(key: &str, timer: &PlatformTimer) -> Result<()> {
|
||||||
// Measure the elapsed time since the hardware timer was started.
|
// Measure the elapsed time since the hardware timer was started.
|
||||||
let elapsed = timer.elapsed_since_lifetime();
|
let elapsed = timer.elapsed_since_lifetime();
|
||||||
Self::set_cstr16(key, &elapsed.as_micros().to_string())
|
Self::VENDOR.set_cstr16(
|
||||||
|
key,
|
||||||
|
&elapsed.as_micros().to_string(),
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system what loader is being used.
|
/// Tell the system what loader is being used.
|
||||||
pub fn set_loader_info() -> Result<()> {
|
pub fn set_loader_info() -> Result<()> {
|
||||||
Self::set_cstr16("LoaderInfo", LOADER_NAME)
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderInfo",
|
||||||
|
LOADER_NAME,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system the relative path to the partition root of the current bootloader.
|
/// Tell the system the relative path to the partition root of the current bootloader.
|
||||||
pub fn set_loader_path(path: &DevicePath) -> Result<()> {
|
pub fn set_loader_path(path: &DevicePath) -> Result<()> {
|
||||||
let subpath = device_path_subpath(path).context("unable to get loader path subpath")?;
|
let subpath = device_path_subpath(path).context("unable to get loader path subpath")?;
|
||||||
Self::set_cstr16("LoaderImageIdentifier", &subpath)
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderImageIdentifier",
|
||||||
|
&subpath,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system what the partition GUID of the ESP Sprout was booted from is.
|
/// Tell the system what the partition GUID of the ESP Sprout was booted from is.
|
||||||
pub fn set_partition_guid(guid: &Guid) -> Result<()> {
|
pub fn set_partition_guid(guid: &Guid) -> Result<()> {
|
||||||
Self::set_cstr16("LoaderDevicePartUUID", &guid.to_string())
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderDevicePartUUID",
|
||||||
|
&guid.to_string(),
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system what boot entries are available.
|
/// Tell the system what boot entries are available.
|
||||||
@@ -69,17 +88,29 @@ impl BootloaderInterface {
|
|||||||
// Write the bytes (including the null terminator) into the data buffer.
|
// Write the bytes (including the null terminator) into the data buffer.
|
||||||
data.extend_from_slice(&encoded);
|
data.extend_from_slice(&encoded);
|
||||||
}
|
}
|
||||||
Self::set("LoaderEntries", &data)
|
Self::VENDOR.set(
|
||||||
|
"LoaderEntries",
|
||||||
|
&data,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system what the default boot entry is.
|
/// Tell the system what the default boot entry is.
|
||||||
pub fn set_default_entry(entry: String) -> Result<()> {
|
pub fn set_default_entry(entry: String) -> Result<()> {
|
||||||
Self::set_cstr16("LoaderEntryDefault", &entry)
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderEntryDefault",
|
||||||
|
&entry,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system what the selected boot entry is.
|
/// Tell the system what the selected boot entry is.
|
||||||
pub fn set_selected_entry(entry: String) -> Result<()> {
|
pub fn set_selected_entry(entry: String) -> Result<()> {
|
||||||
Self::set_cstr16("LoaderEntrySelected", &entry)
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderEntrySelected",
|
||||||
|
&entry,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell the system about the UEFI firmware we are running on.
|
/// Tell the system about the UEFI firmware we are running on.
|
||||||
@@ -91,35 +122,18 @@ impl BootloaderInterface {
|
|||||||
uefi::system::firmware_revision() >> 16,
|
uefi::system::firmware_revision() >> 16,
|
||||||
uefi::system::firmware_revision() & 0xFFFFF,
|
uefi::system::firmware_revision() & 0xFFFFF,
|
||||||
);
|
);
|
||||||
Self::set_cstr16("LoaderFirmwareInfo", &firmware_info)?;
|
Self::VENDOR.set_cstr16(
|
||||||
|
"LoaderFirmwareInfo",
|
||||||
|
&firmware_info,
|
||||||
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Format the firmware revision into something human-readable.
|
// Format the firmware revision into something human-readable.
|
||||||
let firmware_type = format!("UEFI {:02}", uefi::system::firmware_revision());
|
let firmware_type = format!("UEFI {:02}", uefi::system::firmware_revision());
|
||||||
Self::set_cstr16("LoaderFirmwareType", &firmware_type)
|
Self::VENDOR.set_cstr16(
|
||||||
}
|
"LoaderFirmwareType",
|
||||||
|
&firmware_type,
|
||||||
/// The [VariableAttributes] for bootloader interface variables.
|
VariableClass::BootAndRuntimeTemporary,
|
||||||
fn attributes() -> VariableAttributes {
|
)
|
||||||
VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a bootloader interface variable specified by `key` to `value`.
|
|
||||||
fn set(key: &str, value: &[u8]) -> Result<()> {
|
|
||||||
let name =
|
|
||||||
CString16::try_from(key).context("unable to convert variable name to CString16")?;
|
|
||||||
uefi::runtime::set_variable(&name, &Self::VENDOR, Self::attributes(), value)
|
|
||||||
.with_context(|| format!("unable to set efi variable {}", key))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a bootloader interface variable specified by `key` to `value`, converting the value to
|
|
||||||
/// a [CString16].
|
|
||||||
fn set_cstr16(key: &str, value: &str) -> Result<()> {
|
|
||||||
// Encode the value as a CString16 little endian.
|
|
||||||
let encoded = value
|
|
||||||
.encode_utf16()
|
|
||||||
.flat_map(|c| c.to_le_bytes())
|
|
||||||
.collect::<Vec<u8>>();
|
|
||||||
Self::set(key, &encoded)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use crate::options::SproutOptions;
|
|||||||
use crate::options::parser::OptionsRepresentable;
|
use crate::options::parser::OptionsRepresentable;
|
||||||
use crate::phases::phase;
|
use crate::phases::phase;
|
||||||
use crate::platform::timer::PlatformTimer;
|
use crate::platform::timer::PlatformTimer;
|
||||||
|
use crate::secure::SecureBoot;
|
||||||
use crate::utils::PartitionGuidForm;
|
use crate::utils::PartitionGuidForm;
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
@@ -57,6 +58,9 @@ pub mod integrations;
|
|||||||
/// phases: Hooks into specific parts of the boot process.
|
/// phases: Hooks into specific parts of the boot process.
|
||||||
pub mod phases;
|
pub mod phases;
|
||||||
|
|
||||||
|
/// secure: Secure Boot support.
|
||||||
|
pub mod secure;
|
||||||
|
|
||||||
/// setup: Code that initializes the UEFI environment for Sprout.
|
/// setup: Code that initializes the UEFI environment for Sprout.
|
||||||
pub mod setup;
|
pub mod setup;
|
||||||
|
|
||||||
@@ -68,6 +72,11 @@ pub mod utils;
|
|||||||
|
|
||||||
/// Run Sprout, returning an error if one occurs.
|
/// Run Sprout, returning an error if one occurs.
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
|
// For safety reasons, we will bail early if Secure Boot is enabled.
|
||||||
|
if SecureBoot::enabled().context("unable to determine Secure Boot status")? {
|
||||||
|
bail!("Secure Boot is enabled. Sprout does not currently support Secure Boot.");
|
||||||
|
}
|
||||||
|
|
||||||
// Start the platform timer.
|
// Start the platform timer.
|
||||||
let timer = PlatformTimer::start();
|
let timer = PlatformTimer::start();
|
||||||
|
|
||||||
|
|||||||
14
src/secure.rs
Normal file
14
src/secure.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
use crate::utils::variables::VariableController;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
/// Secure boot services.
|
||||||
|
pub struct SecureBoot;
|
||||||
|
|
||||||
|
impl SecureBoot {
|
||||||
|
/// Checks if Secure Boot is enabled on the system.
|
||||||
|
/// This might fail if retrieving the variable fails in an irrecoverable way.
|
||||||
|
pub fn enabled() -> Result<bool> {
|
||||||
|
// The SecureBoot variable will tell us whether Secure Boot is enabled at all.
|
||||||
|
VariableController::GLOBAL.get_bool("SecureBoot")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,9 @@ pub mod framebuffer;
|
|||||||
/// Support code for the media loader protocol.
|
/// Support code for the media loader protocol.
|
||||||
pub mod media_loader;
|
pub mod media_loader;
|
||||||
|
|
||||||
|
/// Support code for EFI variables.
|
||||||
|
pub mod variables;
|
||||||
|
|
||||||
/// Parses the input `path` as a [DevicePath].
|
/// Parses the input `path` as a [DevicePath].
|
||||||
/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol.
|
/// Uses the [DevicePathFromText] protocol exclusively, and will fail if it cannot acquire the protocol.
|
||||||
pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {
|
pub fn text_to_device_path(path: &str) -> Result<PoolDevicePath> {
|
||||||
|
|||||||
93
src/utils/variables.rs
Normal file
93
src/utils/variables.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use uefi::{CString16, guid};
|
||||||
|
use uefi_raw::Status;
|
||||||
|
use uefi_raw::table::runtime::{VariableAttributes, VariableVendor};
|
||||||
|
|
||||||
|
/// The classification of a variable.
|
||||||
|
/// This is an abstraction over various variable attributes.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum VariableClass {
|
||||||
|
/// The variable is available in Boot Services and Runtime Services and is not persistent.
|
||||||
|
BootAndRuntimeTemporary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariableClass {
|
||||||
|
/// The [VariableAttributes] for this classification.
|
||||||
|
fn attributes(&self) -> VariableAttributes {
|
||||||
|
match self {
|
||||||
|
VariableClass::BootAndRuntimeTemporary => {
|
||||||
|
VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provides access to a particular set of vendor variables.
|
||||||
|
pub struct VariableController {
|
||||||
|
/// The GUID of the vendor.
|
||||||
|
vendor: VariableVendor,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VariableController {
|
||||||
|
/// Global variables.
|
||||||
|
pub const GLOBAL: VariableController = VariableController::new(VariableVendor(guid!(
|
||||||
|
"8be4df61-93ca-11d2-aa0d-00e098032b8c"
|
||||||
|
)));
|
||||||
|
|
||||||
|
/// Create a new [VariableController] for the `vendor`.
|
||||||
|
pub const fn new(vendor: VariableVendor) -> Self {
|
||||||
|
Self { vendor }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert `key` to a variable name as a CString16.
|
||||||
|
fn name(key: &str) -> Result<CString16> {
|
||||||
|
CString16::try_from(key).context("unable to convert variable name to CString16")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve a boolean value specified by the `key`.
|
||||||
|
pub fn get_bool(&self, key: &str) -> Result<bool> {
|
||||||
|
let name = Self::name(key)?;
|
||||||
|
|
||||||
|
// Retrieve the variable data, handling variable not existing as false.
|
||||||
|
match uefi::runtime::get_variable_boxed(&name, &self.vendor) {
|
||||||
|
Ok((data, _)) => {
|
||||||
|
// If the variable is zero-length, we treat it as false.
|
||||||
|
if data.is_empty() {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
// We treat the variable as true if the first byte is non-zero.
|
||||||
|
Ok(data[0] > 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(error) => {
|
||||||
|
// If the variable does not exist, we treat it as false.
|
||||||
|
if error.status() == Status::NOT_FOUND {
|
||||||
|
Ok(false)
|
||||||
|
} else {
|
||||||
|
Err(error).with_context(|| format!("unable to get efi variable {}", key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a variable specified by `key` to `value`.
|
||||||
|
/// The variable `class` controls the attributes for the variable.
|
||||||
|
pub fn set(&self, key: &str, value: &[u8], class: VariableClass) -> Result<()> {
|
||||||
|
let name = Self::name(key)?;
|
||||||
|
uefi::runtime::set_variable(&name, &self.vendor, class.attributes(), value)
|
||||||
|
.with_context(|| format!("unable to set efi variable {}", key))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a variable specified by `key` to `value`, converting the value to
|
||||||
|
/// a [CString16]. The variable `class` controls the attributes for the variable.
|
||||||
|
pub fn set_cstr16(&self, key: &str, value: &str, class: VariableClass) -> Result<()> {
|
||||||
|
// Encode the value as a CString16 little endian.
|
||||||
|
let encoded = value
|
||||||
|
.encode_utf16()
|
||||||
|
.flat_map(|c| c.to_le_bytes())
|
||||||
|
.collect::<Vec<u8>>();
|
||||||
|
self.set(key, &encoded, class)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user