feat(boot): basic support for secure boot via shim protocol

This commit is contained in:
2025-10-30 22:56:01 -04:00
parent 92f611e9a8
commit f593f5a601
13 changed files with 331 additions and 28 deletions

View File

@@ -18,7 +18,7 @@ existing UEFI bootloader or booted by the hardware directly.
Sprout is licensed under Apache 2.0 and is open to modifications and contributions. Sprout is licensed under Apache 2.0 and is open to modifications and contributions.
**IMPORTANT WARNING**: Sprout does not support UEFI Secure Boot yet. **IMPORTANT WARNING**: Sprout does not support all of UEFI Secure Boot yet.
See [this issue](https://github.com/edera-dev/sprout/issues/20) for updates. See [this issue](https://github.com/edera-dev/sprout/issues/20) for updates.
## Background ## Background
@@ -65,13 +65,13 @@ The boot menu mechanism is very rudimentary.
- [x] Load Linux initrd from disk - [x] Load Linux initrd from disk
- [x] Basic boot menu - [x] Basic boot menu
- [x] BLS autoconfiguration support - [x] BLS autoconfiguration support
- [x] [Secure Boot support](https://github.com/edera-dev/sprout/issues/20): partial
### Roadmap ### Roadmap
- [ ] [Bootloader interface support](https://github.com/edera-dev/sprout/issues/21) - [ ] [Bootloader interface support](https://github.com/edera-dev/sprout/issues/21)
- [ ] [BLS specification conformance](https://github.com/edera-dev/sprout/issues/2) - [ ] [BLS specification conformance](https://github.com/edera-dev/sprout/issues/2)
- [ ] [Full-featured boot menu](https://github.com/edera-dev/sprout/issues/1) - [ ] [Full-featured boot menu](https://github.com/edera-dev/sprout/issues/1)
- [ ] [Secure Boot support](https://github.com/edera-dev/sprout/issues/20): work in progress
- [ ] [UKI support](https://github.com/edera-dev/sprout/issues/6): partial - [ ] [UKI support](https://github.com/edera-dev/sprout/issues/6): partial
- [ ] [multiboot2 support](https://github.com/edera-dev/sprout/issues/7) - [ ] [multiboot2 support](https://github.com/edera-dev/sprout/issues/7)
- [ ] [Linux boot protocol (boot without EFI stub)](https://github.com/edera-dev/sprout/issues/7) - [ ] [Linux boot protocol (boot without EFI stub)](https://github.com/edera-dev/sprout/issues/7)

View File

@@ -2,7 +2,7 @@
## Prerequisites ## Prerequisites
- Secure Boot disabled - Secure Boot is disabled or configured to allow Sprout
- UEFI Windows installation - UEFI Windows installation
## Step 1: Base Installation ## Step 1: Base Installation

View File

@@ -36,7 +36,7 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
// Resolve the path to the image to chainload. // Resolve the path to the image to chainload.
let resolved = utils::resolve_path( let resolved = utils::resolve_path(
context.root().loaded_image_path()?, Some(context.root().loaded_image_path()?),
&context.stamp(&configuration.path), &context.stamp(&configuration.path),
) )
.context("unable to resolve chainload path")?; .context("unable to resolve chainload path")?;
@@ -90,7 +90,8 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
// If an initrd is provided, register it with the EFI stack. // If an initrd is provided, register it with the EFI stack.
let mut initrd_handle = None; let mut initrd_handle = None;
if let Some(linux_initrd) = initrd { if let Some(linux_initrd) = initrd {
let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd) let content =
utils::read_file_contents(Some(context.root().loaded_image_path()?), &linux_initrd)
.context("unable to read linux initrd")?; .context("unable to read linux initrd")?;
let handle = let handle =
MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice()) MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice())

View File

@@ -98,7 +98,7 @@ fn register_media_loader_file(
// Stamp the path to the file. // Stamp the path to the file.
let path = context.stamp(path); let path = context.stamp(path);
// Read the file contents. // Read the file contents.
let content = utils::read_file_contents(context.root().loaded_image_path()?, &path) let content = utils::read_file_contents(Some(context.root().loaded_image_path()?), &path)
.context(format!("unable to read {} file", what))?; .context(format!("unable to read {} file", what))?;
// Register the media loader. // Register the media loader.
let handle = MediaLoaderHandle::register(guid, content.into_boxed_slice()) let handle = MediaLoaderHandle::register(guid, content.into_boxed_slice())

View File

@@ -143,7 +143,7 @@ pub fn splash(context: Rc<SproutContext>, configuration: &SplashConfiguration) -
// Stamp the image path value. // Stamp the image path value.
let image = context.stamp(&configuration.image); let image = context.stamp(&configuration.image);
// Read the image contents. // Read the image contents.
let image = read_file_contents(context.root().loaded_image_path()?, &image)?; let image = read_file_contents(Some(context.root().loaded_image_path()?), &image)?;
// Decode the image as a PNG. // Decode the image as a PNG.
let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png) let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png)
.decode() .decode()

View File

@@ -19,7 +19,7 @@ fn load_raw_config(options: &SproutOptions) -> Result<Vec<u8>> {
info!("configuration file: {}", options.config); info!("configuration file: {}", options.config);
// Read the contents of the sprout config file. // Read the contents of the sprout config file.
let content = utils::read_file_contents(&path, &options.config) let content = utils::read_file_contents(Some(&path), &options.config)
.context("unable to read sprout config file")?; .context("unable to read sprout config file")?;
// Return the contents of the sprout config file. // Return the contents of the sprout config file.
Ok(content) Ok(content)

View File

@@ -26,7 +26,7 @@ fn load_driver(context: Rc<SproutContext>, driver: &DriverDeclaration) -> Result
// Resolve the path to the driver image. // Resolve the path to the driver image.
let resolved = utils::resolve_path( let resolved = utils::resolve_path(
context.root().loaded_image_path()?, Some(context.root().loaded_image_path()?),
&context.stamp(&driver.path), &context.stamp(&driver.path),
) )
.context("unable to resolve path to driver")?; .context("unable to resolve path to driver")?;

View File

@@ -49,7 +49,7 @@ pub fn generate(context: Rc<SproutContext>, bls: &BlsConfiguration) -> Result<Ve
let path = context.stamp(&bls.path); let path = context.stamp(&bls.path);
// Resolve the path to the BLS directory. // Resolve the path to the BLS directory.
let bls_resolved = utils::resolve_path(context.root().loaded_image_path()?, &path) let bls_resolved = utils::resolve_path(Some(context.root().loaded_image_path()?), &path)
.context("unable to resolve bls path")?; .context("unable to resolve bls path")?;
// Construct a filesystem path to the BLS entries directory. // Construct a filesystem path to the BLS entries directory.

View File

@@ -1,13 +1,20 @@
use crate::integrations::shim::hook::SecurityHook;
use crate::utils; use crate::utils;
use crate::utils::ResolvedPath; use crate::utils::ResolvedPath;
use crate::utils::variables::VariableController;
use anyhow::{Context, Result, anyhow, bail}; use anyhow::{Context, Result, anyhow, bail};
use std::ffi::c_void; use std::ffi::c_void;
use uefi::Handle; use uefi::Handle;
use uefi::boot::LoadImageSource; use uefi::boot::LoadImageSource;
use uefi::proto::device_path::DevicePath; use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
use uefi::proto::device_path::{DevicePath, FfiDevicePath};
use uefi::proto::unsafe_protocol; use uefi::proto::unsafe_protocol;
use uefi_raw::table::runtime::VariableVendor;
use uefi_raw::{Guid, Status, guid}; use uefi_raw::{Guid, Status, guid};
/// Security hook support.
mod hook;
/// Support for the shim loader application for Secure Boot. /// Support for the shim loader application for Secure Boot.
pub struct ShimSupport; pub struct ShimSupport;
@@ -17,6 +24,12 @@ pub enum ShimInput<'a> {
OwnedDataBuffer(Option<&'a ResolvedPath>, Vec<u8>), OwnedDataBuffer(Option<&'a ResolvedPath>, Vec<u8>),
/// Data loaded into a buffer and ready to be verified. /// Data loaded into a buffer and ready to be verified.
DataBuffer(Option<&'a ResolvedPath>, &'a [u8]), DataBuffer(Option<&'a ResolvedPath>, &'a [u8]),
/// Low-level data buffer provided by the security hook.
SecurityHookBuffer(Option<*const FfiDevicePath>, &'a [u8]),
/// Low-level owned data buffer provided by the security hook.
SecurityHookOwnedBuffer(Option<*const FfiDevicePath>, Vec<u8>),
/// Low-level path provided by the security hook.
SecurityHookPath(*const FfiDevicePath),
/// Data is provided as a resolved path. We will need to load the data to verify it. /// Data is provided as a resolved path. We will need to load the data to verify it.
/// The output will them return the loaded data. /// The output will them return the loaded data.
ResolvedPath(&'a ResolvedPath), ResolvedPath(&'a ResolvedPath),
@@ -27,6 +40,9 @@ impl<'a> ShimInput<'a> {
pub fn buffer(&self) -> Option<&[u8]> { pub fn buffer(&self) -> Option<&[u8]> {
match self { match self {
ShimInput::OwnedDataBuffer(_, data) => Some(data), ShimInput::OwnedDataBuffer(_, data) => Some(data),
ShimInput::SecurityHookOwnedBuffer(_, data) => Some(data),
ShimInput::SecurityHookBuffer(_, data) => Some(data),
ShimInput::SecurityHookPath(_) => None,
ShimInput::DataBuffer(_, data) => Some(data), ShimInput::DataBuffer(_, data) => Some(data),
ShimInput::ResolvedPath(_) => None, ShimInput::ResolvedPath(_) => None,
} }
@@ -37,7 +53,14 @@ impl<'a> ShimInput<'a> {
match self { match self {
ShimInput::OwnedDataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()), ShimInput::OwnedDataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()),
ShimInput::DataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()), ShimInput::DataBuffer(path, _) => path.as_ref().map(|it| it.full_path.as_ref()),
ShimInput::SecurityHookBuffer(path, _) => {
path.map(|it| unsafe { DevicePath::from_ffi_ptr(it) })
}
ShimInput::SecurityHookPath(path) => unsafe { Some(DevicePath::from_ffi_ptr(*path)) },
ShimInput::ResolvedPath(path) => Some(path.full_path.as_ref()), ShimInput::ResolvedPath(path) => Some(path.full_path.as_ref()),
ShimInput::SecurityHookOwnedBuffer(path, _) => {
path.map(|it| unsafe { DevicePath::from_ffi_ptr(it) })
}
} }
} }
@@ -51,9 +74,33 @@ impl<'a> ShimInput<'a> {
Ok(ShimInput::OwnedDataBuffer(root, data.to_vec())) Ok(ShimInput::OwnedDataBuffer(root, data.to_vec()))
} }
ShimInput::SecurityHookPath(ffi_path) => {
// Acquire the file path.
let Some(path) = self.file_path() else {
bail!("unable to convert security hook path to device path");
};
// Convert the underlying path to a string.
let path = path
.to_string(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert device path to string")?;
let path = utils::resolve_path(None, &path.to_string())
.context("unable to resolve path")?;
// Read the file path.
let data = path.read_file()?;
Ok(ShimInput::SecurityHookOwnedBuffer(Some(ffi_path), data))
}
ShimInput::SecurityHookBuffer(_, _) => {
bail!("unable to convert security hook buffer to owned data buffer")
}
ShimInput::ResolvedPath(path) => { ShimInput::ResolvedPath(path) => {
Ok(ShimInput::OwnedDataBuffer(Some(path), path.read_file()?)) Ok(ShimInput::OwnedDataBuffer(Some(path), path.read_file()?))
} }
ShimInput::SecurityHookOwnedBuffer(path, data) => {
Ok(ShimInput::SecurityHookOwnedBuffer(path, data))
}
} }
} }
} }
@@ -83,6 +130,10 @@ struct ShimLockProtocol {
} }
impl ShimSupport { impl ShimSupport {
/// Variable controller for the shim lock.
const SHIM_LOCK_VARIABLES: VariableController =
VariableController::new(VariableVendor(Self::SHIM_LOCK_GUID));
/// GUID for the shim lock protocol. /// GUID for the shim lock protocol.
const SHIM_LOCK_GUID: Guid = guid!("605dab50-e046-4300-abb6-3dd810dd8b23"); const SHIM_LOCK_GUID: Guid = guid!("605dab50-e046-4300-abb6-3dd810dd8b23");
/// GUID for the shim image loader protocol. /// GUID for the shim image loader protocol.
@@ -103,7 +154,7 @@ impl ShimSupport {
} }
/// Use the shim to validate the `input`, returning [ShimVerificationOutput] when complete. /// Use the shim to validate the `input`, returning [ShimVerificationOutput] when complete.
pub fn validate(input: ShimInput) -> Result<ShimVerificationOutput> { pub fn verify(input: ShimInput) -> Result<ShimVerificationOutput> {
// Acquire the handle to the shim lock protocol. // Acquire the handle to the shim lock protocol.
let handle = utils::find_handle(&Self::SHIM_LOCK_GUID) let handle = utils::find_handle(&Self::SHIM_LOCK_GUID)
.context("unable to find shim lock protocol")? .context("unable to find shim lock protocol")?
@@ -117,21 +168,27 @@ impl ShimSupport {
ShimInput::OwnedDataBuffer(_, _data) => { ShimInput::OwnedDataBuffer(_, _data) => {
bail!("owned data buffer is not supported in the verification function"); bail!("owned data buffer is not supported in the verification function");
} }
ShimInput::SecurityHookBuffer(_, _) => None,
ShimInput::SecurityHookOwnedBuffer(_, _) => None,
ShimInput::DataBuffer(_, _) => None, ShimInput::DataBuffer(_, _) => None,
ShimInput::ResolvedPath(path) => Some(path.read_file()?), ShimInput::ResolvedPath(path) => Some(path.read_file()?),
ShimInput::SecurityHookPath(_) => None,
}; };
// Convert the input to a buffer. // Convert the input to a buffer.
// If the input provides the data buffer, we will use that. // If the input provides the data buffer, we will use that.
// Otherwise, we will use the data loaded by this function. // Otherwise, we will use the data loaded by this function.
let buffer = match input { let buffer = match &input {
ShimInput::OwnedDataBuffer(_root, _data) => { ShimInput::OwnedDataBuffer(_root, data) => data,
bail!("owned data buffer is not supported in the verification function"); ShimInput::DataBuffer(_root, data) => *data,
}
ShimInput::DataBuffer(_root, data) => data,
ShimInput::ResolvedPath(_path) => maybe_loaded_data ShimInput::ResolvedPath(_path) => maybe_loaded_data
.as_deref() .as_deref()
.context("expected data buffer to be loaded already")?, .context("expected data buffer to be loaded already")?,
ShimInput::SecurityHookBuffer(_, data) => data,
ShimInput::SecurityHookOwnedBuffer(_, data) => data,
ShimInput::SecurityHookPath(_) => {
bail!("security hook path input not supported in the verification function")
}
}; };
// Check if the buffer is too large to verify. // Check if the buffer is too large to verify.
@@ -168,12 +225,19 @@ impl ShimSupport {
// Determines whether LoadImage in Boot Services must be patched. // Determines whether LoadImage in Boot Services must be patched.
// Version 16 of the shim doesn't require extra effort to load Secure Boot binaries. // Version 16 of the shim doesn't require extra effort to load Secure Boot binaries.
// If the image loader is installed, we can skip over the security override. // If the image loader is installed, we can skip over the security hook.
let requires_security_override = shim_loaded && !shim_loader_available; let requires_security_hook = shim_loaded && !shim_loader_available;
// If the security override is required, we will bail for now. // If the security hook is required, we will bail for now.
if requires_security_override { if requires_security_hook {
bail!("shim image loader protocol is not available, please upgrade to shim version 16"); // Install the security hook, if possible. If it's not, this is necessary to continue
// so we should bail.
let installed = SecurityHook::install().context("unable to install security hook")?;
if !installed {
bail!("unable to install security hook require for this platform");
}
// Retain the shim protocol after load.
Self::retain()?
} }
// Converts the shim input to an owned data buffer. // Converts the shim input to an owned data buffer.
@@ -188,6 +252,21 @@ impl ShimSupport {
}; };
// Loads the image using Boot Services LoadImage function. // Loads the image using Boot Services LoadImage function.
uefi::boot::load_image(current_image, source).context("unable to load image") let result = uefi::boot::load_image(current_image, source).context("unable to load image");
// If the security override is required, we will uninstall the security hook.
if requires_security_hook {
SecurityHook::uninstall().context("unable to uninstall security hook")?;
}
result
}
/// Set the ShimRetainProtocol variable to indicate that shim should retain the protocols
/// for the full lifetime of boot services.
pub fn retain() -> Result<()> {
Self::SHIM_LOCK_VARIABLES
.set_bool("ShimRetainProtocol", true)
.context("unable to retain shim protocol")?;
Ok(())
} }
} }

View File

@@ -0,0 +1,214 @@
use crate::integrations::shim::{ShimInput, ShimSupport, ShimVerificationOutput};
use crate::utils;
use anyhow::{Context, Result, bail};
use log::warn;
use std::sync::{LazyLock, Mutex};
use uefi::proto::device_path::FfiDevicePath;
use uefi::proto::unsafe_protocol;
use uefi::{Guid, guid};
use uefi_raw::Status;
/// GUID for the EFI_SECURITY_ARCH protocol.
const SECURITY_ARCH_GUID: Guid = guid!("a46423e3-4617-49f1-b9ff-d1bfa9115839");
/// GUID for the EFI_SECURITY_ARCH2 protocol.
const SECURITY_ARCH2_GUID: Guid = guid!("94ab2f58-1438-4ef1-9152-18941a3a0e68");
/// EFI_SECURITY_ARCH protocol definition.
#[unsafe_protocol(SECURITY_ARCH_GUID)]
pub struct SecurityArchProtocol {
/// Determines the file authentication state.
pub file_authentication_state: unsafe extern "efiapi" fn(
this: *const SecurityArchProtocol,
status: u32,
path: *mut FfiDevicePath,
) -> Status,
}
/// EFI_SECURITY_ARCH2 protocol definition.
#[unsafe_protocol(SECURITY_ARCH2_GUID)]
pub struct SecurityArch2Protocol {
/// Determines the file authentication.
pub file_authentication: unsafe extern "efiapi" fn(
this: *const SecurityArch2Protocol,
path: *mut FfiDevicePath,
file_buffer: *mut u8,
file_size: usize,
boot_policy: bool,
) -> Status,
}
/// Global state for the security hook.
struct SecurityHookState {
original_hook: SecurityArchProtocol,
original_hook2: SecurityArch2Protocol,
}
/// Global state for the security hook.
/// This is messy, but it is safe given the mutex.
static GLOBAL_HOOK_STATE: LazyLock<Mutex<Option<SecurityHookState>>> =
LazyLock::new(|| Mutex::new(None));
/// Security hook helper.
pub struct SecurityHook;
impl SecurityHook {
/// Shared verifier logic for both hook types.
fn verify(input: ShimInput) -> Status {
// Verify the input.
match ShimSupport::verify(input) {
Ok(output) => match output {
// If the verification failed, return the access-denied status.
ShimVerificationOutput::VerificationFailed => Status::ACCESS_DENIED,
// If the verification succeeded, return the success status.
ShimVerificationOutput::VerifiedDataNotLoaded => Status::SUCCESS,
ShimVerificationOutput::VerifiedDataBuffer(_) => Status::SUCCESS,
},
// If an error occurs, log the error since we can't return a better error.
// Then return the access-denied status.
Err(error) => {
warn!("unable to verify image: {}", error);
Status::ACCESS_DENIED
}
}
}
/// File authentication state verifier for the EFI_SECURITY_ARCH protocol.
/// Takes the `path` and determines the verification.
unsafe extern "efiapi" fn arch_file_authentication_state(
_this: *const SecurityArchProtocol,
_status: u32,
path: *mut FfiDevicePath,
) -> Status {
// Verify the path is not null.
if path.is_null() {
return Status::INVALID_PARAMETER;
}
// Construct a shim input from the path.
let input = ShimInput::SecurityHookPath(path);
// Verify the input.
Self::verify(input)
}
/// File authentication verifier for the EFI_SECURITY_ARCH2 protocol.
/// Takes the `path` and a file buffer to determine the verification.
unsafe extern "efiapi" fn arch2_file_authentication(
_this: *const SecurityArch2Protocol,
path: *mut FfiDevicePath,
file_buffer: *mut u8,
file_size: usize,
boot_policy: bool,
) -> Status {
// Verify the path and file buffer are not null.
if path.is_null() || file_buffer.is_null() {
return Status::INVALID_PARAMETER;
}
// If the boot policy is true, we can't continue as we don't support that.
if boot_policy {
return Status::INVALID_PARAMETER;
}
// Construct a slice out of the file buffer and size.
let buffer = unsafe { std::slice::from_raw_parts_mut(file_buffer, file_size) };
// Construct a shim input from the path.
let input = ShimInput::SecurityHookBuffer(Some(path), buffer);
// Verify the input.
Self::verify(input)
}
/// Install the security hook if needed.
pub fn install() -> Result<bool> {
// Find the security arch protocol. If we can't find it, we will return false.
let Some(hook_arch) = utils::find_handle(&SECURITY_ARCH_GUID)
.context("unable to check security arch existence")?
else {
return Ok(false);
};
// Find the security arch2 protocol. If we can't find it, we will return false.
let Some(hook_arch2) = utils::find_handle(&SECURITY_ARCH2_GUID)
.context("unable to check security arch2 existence")?
else {
return Ok(false);
};
// Open the security arch protocol.
let mut arch_protocol =
uefi::boot::open_protocol_exclusive::<SecurityArchProtocol>(hook_arch)
.context("unable to open security arch protocol")?;
// Open the security arch2 protocol.
let mut arch_protocol2 =
uefi::boot::open_protocol_exclusive::<SecurityArch2Protocol>(hook_arch2)
.context("unable to open security arch2 protocol")?;
// Construct the global state to store.
let state = SecurityHookState {
original_hook: SecurityArchProtocol {
file_authentication_state: arch_protocol.file_authentication_state,
},
original_hook2: SecurityArch2Protocol {
file_authentication: arch_protocol2.file_authentication,
},
};
// Acquire the lock to the global state and replace it.
let Ok(mut global_state) = GLOBAL_HOOK_STATE.lock() else {
bail!("unable to acquire global hook state lock");
};
global_state.replace(state);
// Install the hooks into the UEFI stack.
arch_protocol.file_authentication_state = Self::arch_file_authentication_state;
arch_protocol2.file_authentication = Self::arch2_file_authentication;
Ok(true)
}
/// Uninstalls the global security hook, if installed.
pub fn uninstall() -> Result<()> {
// Find the security arch protocol. If we can't find it, we will do nothing.
let Some(hook_arch) = utils::find_handle(&SECURITY_ARCH_GUID)
.context("unable to check security arch existence")?
else {
return Ok(());
};
// Find the security arch2 protocol. If we can't find it, we will do nothing.
let Some(hook_arch2) = utils::find_handle(&SECURITY_ARCH2_GUID)
.context("unable to check security arch2 existence")?
else {
return Ok(());
};
// Open the security arch protocol.
let mut arch_protocol =
uefi::boot::open_protocol_exclusive::<SecurityArchProtocol>(hook_arch)
.context("unable to open security arch protocol")?;
// Open the security arch2 protocol.
let mut arch_protocol2 =
uefi::boot::open_protocol_exclusive::<SecurityArch2Protocol>(hook_arch2)
.context("unable to open security arch2 protocol")?;
// Acquire the lock to the global state.
let Ok(mut global_state) = GLOBAL_HOOK_STATE.lock() else {
bail!("unable to acquire global hook state lock");
};
// Take the state and replace the original functions.
let Some(state) = global_state.take() else {
return Ok(());
};
// Reinstall the original functions.
arch_protocol.file_authentication_state = state.original_hook.file_authentication_state;
arch_protocol2.file_authentication = state.original_hook2.file_authentication;
Ok(())
}
}

View File

@@ -72,9 +72,9 @@ 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. // For safety reasons, we will note that Secure Boot is in beta on Sprout.
if SecureBoot::enabled().context("unable to determine Secure Boot status")? { if SecureBoot::enabled().context("unable to determine Secure Boot status")? {
warn!("Secure Boot is enabled. Sprout does not currently support Secure Boot."); warn!("Secure Boot is enabled. Sprout Secure Boot is in beta.");
} }
// Start the platform timer. // Start the platform timer.

View File

@@ -121,7 +121,7 @@ impl ResolvedPath {
/// Resolve a path specified by `input` to its various components. /// Resolve a path specified by `input` to its various components.
/// Uses `default_root_path` as the base root if one is not specified in the path. /// Uses `default_root_path` as the base root if one is not specified in the path.
/// Returns [ResolvedPath] which contains the resolved components. /// Returns [ResolvedPath] which contains the resolved components.
pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result<ResolvedPath> { pub fn resolve_path(default_root_path: Option<&DevicePath>, input: &str) -> Result<ResolvedPath> {
let mut path = text_to_device_path(input).context("unable to convert text to path")?; let mut path = text_to_device_path(input).context("unable to convert text to path")?;
let path_has_device = path let path_has_device = path
.node_iter() .node_iter()
@@ -137,6 +137,9 @@ pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result<Resol
if !input.starts_with('\\') { if !input.starts_with('\\') {
input.insert(0, '\\'); input.insert(0, '\\');
} }
let default_root_path = default_root_path.context("unable to get default root path")?;
input.insert_str( input.insert_str(
0, 0,
device_path_root(default_root_path) device_path_root(default_root_path)
@@ -170,7 +173,7 @@ pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result<Resol
/// This acquires exclusive protocol access to the [SimpleFileSystem] protocol of the resolved /// This acquires exclusive protocol access to the [SimpleFileSystem] protocol of the resolved
/// filesystem handle, so care must be taken to call this function outside a scope with /// filesystem handle, so care must be taken to call this function outside a scope with
/// the filesystem handle protocol acquired. /// the filesystem handle protocol acquired.
pub fn read_file_contents(default_root_path: &DevicePath, input: &str) -> Result<Vec<u8>> { pub fn read_file_contents(default_root_path: Option<&DevicePath>, input: &str) -> Result<Vec<u8>> {
let resolved = resolve_path(default_root_path, input)?; let resolved = resolve_path(default_root_path, input)?;
resolved.read_file() resolved.read_file()
} }

View File

@@ -90,4 +90,10 @@ impl VariableController {
.collect::<Vec<u8>>(); .collect::<Vec<u8>>();
self.set(key, &encoded, class) self.set(key, &encoded, class)
} }
/// Set a boolean variable specified by `key` to `value`, converting the value.
/// The variable `class` controls the attributes for the variable.
pub fn set_bool(&self, key: &str, value: bool) -> Result<()> {
self.set(key, &[value as u8], VariableClass::BootAndRuntimeTemporary)
}
} }