diff --git a/README.md b/README.md index adca9c2..50197bd 100644 --- a/README.md +++ b/README.md @@ -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. -**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. ## Background @@ -65,13 +65,13 @@ The boot menu mechanism is very rudimentary. - [x] Load Linux initrd from disk - [x] Basic boot menu - [x] BLS autoconfiguration support +- [x] [Secure Boot support](https://github.com/edera-dev/sprout/issues/20): partial ### Roadmap - [ ] [Bootloader interface support](https://github.com/edera-dev/sprout/issues/21) - [ ] [BLS specification conformance](https://github.com/edera-dev/sprout/issues/2) - [ ] [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 - [ ] [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) diff --git a/docs/windows-setup.md b/docs/windows-setup.md index f45ef58..6bd78df 100644 --- a/docs/windows-setup.md +++ b/docs/windows-setup.md @@ -2,7 +2,7 @@ ## Prerequisites -- Secure Boot disabled +- Secure Boot is disabled or configured to allow Sprout - UEFI Windows installation ## Step 1: Base Installation diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 5341c4c..c23985d 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -1,5 +1,6 @@ use crate::context::SproutContext; use crate::integrations::bootloader_interface::BootloaderInterface; +use crate::integrations::shim::{ShimInput, ShimSupport}; use crate::utils; use crate::utils::media_loader::MediaLoaderHandle; use crate::utils::media_loader::constants::linux::LINUX_EFI_INITRD_MEDIA_GUID; @@ -35,20 +36,14 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat // Resolve the path to the image to chainload. let resolved = utils::resolve_path( - context.root().loaded_image_path()?, + Some(context.root().loaded_image_path()?), &context.stamp(&configuration.path), ) .context("unable to resolve chainload path")?; - // Load the image to chainload. - let image = uefi::boot::load_image( - sprout_image, - uefi::boot::LoadImageSource::FromDevicePath { - device_path: &resolved.full_path, - boot_policy: uefi::proto::BootPolicy::ExactMatch, - }, - ) - .context("unable to load image")?; + // Load the image to chainload using the shim support integration. + // It will determine if the image needs to be loaded via the shim or can be loaded directly. + let image = ShimSupport::load(sprout_image, ShimInput::ResolvedPath(&resolved))?; // Open the LoadedImage protocol of the image to chainload. let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) @@ -95,8 +90,9 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat // If an initrd is provided, register it with the EFI stack. let mut initrd_handle = None; if let Some(linux_initrd) = initrd { - let content = utils::read_file_contents(context.root().loaded_image_path()?, &linux_initrd) - .context("unable to read linux initrd")?; + let content = + utils::read_file_contents(Some(context.root().loaded_image_path()?), &linux_initrd) + .context("unable to read linux initrd")?; let handle = MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice()) .context("unable to register linux initrd")?; diff --git a/src/actions/edera.rs b/src/actions/edera.rs index b5a8e1f..b0794de 100644 --- a/src/actions/edera.rs +++ b/src/actions/edera.rs @@ -98,7 +98,7 @@ fn register_media_loader_file( // Stamp the path to the file. let path = context.stamp(path); // 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))?; // Register the media loader. let handle = MediaLoaderHandle::register(guid, content.into_boxed_slice()) diff --git a/src/actions/splash.rs b/src/actions/splash.rs index 173ed43..1cf0691 100644 --- a/src/actions/splash.rs +++ b/src/actions/splash.rs @@ -143,7 +143,7 @@ pub fn splash(context: Rc, configuration: &SplashConfiguration) - // Stamp the image path value. let image = context.stamp(&configuration.image); // 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. let image = ImageReader::with_format(Cursor::new(image), ImageFormat::Png) .decode() diff --git a/src/config/loader.rs b/src/config/loader.rs index d02ed18..6c5342e 100644 --- a/src/config/loader.rs +++ b/src/config/loader.rs @@ -19,7 +19,7 @@ fn load_raw_config(options: &SproutOptions) -> Result> { info!("configuration file: {}", options.config); // 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")?; // Return the contents of the sprout config file. Ok(content) diff --git a/src/drivers.rs b/src/drivers.rs index e58952d..8181be3 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1,4 +1,5 @@ use crate::context::SproutContext; +use crate::integrations::shim::{ShimInput, ShimSupport}; use crate::utils; use anyhow::{Context, Result}; use log::info; @@ -6,7 +7,6 @@ use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::rc::Rc; use uefi::boot::SearchType; -use uefi::proto::device_path::LoadedImageDevicePath; /// Declares a driver configuration. /// Drivers allow extending the functionality of Sprout. @@ -23,28 +23,17 @@ pub struct DriverDeclaration { fn load_driver(context: Rc, driver: &DriverDeclaration) -> Result<()> { // Acquire the handle and device path of the loaded image. let sprout_image = uefi::boot::image_handle(); - let image_device_path_protocol = - uefi::boot::open_protocol_exclusive::(sprout_image) - .context("unable to open loaded image device path protocol")?; - // Get the device path root of the sprout image. - let mut full_path = utils::device_path_root(&image_device_path_protocol)?; - - // Push the path of the driver from the root. - full_path.push_str(&context.stamp(&driver.path)); - - // Convert the path to a device path. - let device_path = utils::text_to_device_path(&full_path)?; - - // Load the driver image. - let image = uefi::boot::load_image( - sprout_image, - uefi::boot::LoadImageSource::FromDevicePath { - device_path: &device_path, - boot_policy: uefi::proto::BootPolicy::ExactMatch, - }, + // Resolve the path to the driver image. + let resolved = utils::resolve_path( + Some(context.root().loaded_image_path()?), + &context.stamp(&driver.path), ) - .context("unable to load image")?; + .context("unable to resolve path to driver")?; + + // Load the driver image using the shim support integration. + // It will determine if the image needs to be loaded via the shim or can be loaded directly. + let image = ShimSupport::load(sprout_image, ShimInput::ResolvedPath(&resolved))?; // Start the driver image, this is expected to return control to sprout. // There is no guarantee that the driver will actually return control as it is diff --git a/src/generators/bls.rs b/src/generators/bls.rs index 4133427..8fcfab2 100644 --- a/src/generators/bls.rs +++ b/src/generators/bls.rs @@ -49,7 +49,7 @@ pub fn generate(context: Rc, bls: &BlsConfiguration) -> Result { + /// Data loaded into a buffer and ready to be verified, owned. + OwnedDataBuffer(Option<&'a ResolvedPath>, Vec), + /// Data loaded into a buffer and ready to be verified. + 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), + /// 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. + /// The output will them return the loaded data. + ResolvedPath(&'a ResolvedPath), +} + +impl<'a> ShimInput<'a> { + /// Accesses the buffer behind the shim input, if available. + pub fn buffer(&self) -> Option<&[u8]> { + match self { + ShimInput::OwnedDataBuffer(_, data) => Some(data), + ShimInput::SecurityHookOwnedBuffer(_, data) => Some(data), + ShimInput::SecurityHookBuffer(_, data) => Some(data), + ShimInput::SecurityHookPath(_) => None, + ShimInput::DataBuffer(_, data) => Some(data), + ShimInput::ResolvedPath(_) => None, + } + } + + /// Accesses the full device path to the input. + pub fn file_path(&self) -> Option<&DevicePath> { + match self { + 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::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::SecurityHookOwnedBuffer(path, _) => { + path.map(|it| unsafe { DevicePath::from_ffi_ptr(it) }) + } + } + } + + /// Converts this input into an owned data buffer, where the data is loaded. + /// For ResolvedPath, this will read the file. + pub fn into_owned_data_buffer(self) -> Result> { + match self { + ShimInput::OwnedDataBuffer(root, data) => Ok(ShimInput::OwnedDataBuffer(root, data)), + + ShimInput::DataBuffer(root, data) => { + 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) => { + Ok(ShimInput::OwnedDataBuffer(Some(path), path.read_file()?)) + } + + ShimInput::SecurityHookOwnedBuffer(path, data) => { + Ok(ShimInput::SecurityHookOwnedBuffer(path, data)) + } + } + } +} + +/// Output of the shim verification function. +/// Since the shim needs to load the data from disk, we will optimize by using that as the data +/// to actually boot. +pub enum ShimVerificationOutput { + /// The verification failed. + VerificationFailed, + /// The data provided to the verifier was already a buffer. + VerifiedDataNotLoaded, + /// Verifying the data resulted in loading the data from the source. + /// This contains the data that was loaded, so it won't need to be loaded again. + VerifiedDataBuffer(Vec), +} + +/// The shim lock protocol as defined by the shim loader application. +#[unsafe_protocol(ShimSupport::SHIM_LOCK_GUID)] +struct ShimLockProtocol { + /// Verify the data in `buffer` with the size `buffer_size` to determine if it is valid. + pub shim_verify: unsafe extern "efiapi" fn(buffer: *mut c_void, buffer_size: u32) -> Status, + /// Unused function that is defined by the shim. + _generate_header: *mut c_void, + /// Unused function that is defined by the shim. + _read_header: *mut c_void, +} + +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. + const SHIM_LOCK_GUID: Guid = guid!("605dab50-e046-4300-abb6-3dd810dd8b23"); + /// GUID for the shim image loader protocol. + const SHIM_IMAGE_LOADER_GUID: Guid = guid!("1f492041-fadb-4e59-9e57-7cafe73a55ab"); + + /// Determines whether the shim is loaded. + pub fn loaded() -> Result { + Ok(utils::find_handle(&Self::SHIM_LOCK_GUID) + .context("unable to find shim lock protocol")? + .is_some()) + } + + /// Determines whether the shim loader is available. + pub fn loader_available() -> Result { + Ok(utils::find_handle(&Self::SHIM_IMAGE_LOADER_GUID) + .context("unable to find shim image loader protocol")? + .is_some()) + } + + /// Use the shim to validate the `input`, returning [ShimVerificationOutput] when complete. + pub fn verify(input: ShimInput) -> Result { + // Acquire the handle to the shim lock protocol. + let handle = utils::find_handle(&Self::SHIM_LOCK_GUID) + .context("unable to find shim lock protocol")? + .ok_or_else(|| anyhow!("unable to find shim lock protocol"))?; + // Acquire the protocol exclusively to the shim lock. + let protocol = uefi::boot::open_protocol_exclusive::(handle) + .context("unable to open shim lock protocol")?; + + // If the input type is a device path, we need to load the data. + let maybe_loaded_data = match input { + ShimInput::OwnedDataBuffer(_, _data) => { + bail!("owned data buffer is not supported in the verification function"); + } + ShimInput::SecurityHookBuffer(_, _) => None, + ShimInput::SecurityHookOwnedBuffer(_, _) => None, + ShimInput::DataBuffer(_, _) => None, + ShimInput::ResolvedPath(path) => Some(path.read_file()?), + ShimInput::SecurityHookPath(_) => None, + }; + + // Convert the input to a buffer. + // If the input provides the data buffer, we will use that. + // Otherwise, we will use the data loaded by this function. + let buffer = match &input { + ShimInput::OwnedDataBuffer(_root, data) => data, + ShimInput::DataBuffer(_root, data) => *data, + ShimInput::ResolvedPath(_path) => maybe_loaded_data + .as_deref() + .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. + if buffer.len() > u32::MAX as usize { + bail!("buffer is too large to verify with shim lock protocol"); + } + + // Call the shim verify function. + // SAFETY: The shim verify function is specified by the shim lock protocol. + // Calling this function is considered safe because + let status = + unsafe { (protocol.shim_verify)(buffer.as_ptr() as *mut c_void, buffer.len() as u32) }; + + // If the verification failed, return the verification failure output. + if !status.is_success() { + return Ok(ShimVerificationOutput::VerificationFailed); + } + + // If verification succeeded, return the validation output, + // which might include the loaded data. + Ok(maybe_loaded_data + .map(ShimVerificationOutput::VerifiedDataBuffer) + .unwrap_or(ShimVerificationOutput::VerifiedDataNotLoaded)) + } + + /// Load the image specified by the `input` and returns an image handle. + pub fn load(current_image: Handle, input: ShimInput) -> Result { + // Determine whether the shim is loaded. + let shim_loaded = Self::loaded().context("unable to determine if shim is loaded")?; + + // Determine whether the shim loader is available. + let shim_loader_available = + Self::loader_available().context("unable to determine if shim loader is available")?; + + // Determines whether LoadImage in Boot Services must be patched. + // 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 hook. + let requires_security_hook = shim_loaded && !shim_loader_available; + + // If the security hook is required, we will bail for now. + if requires_security_hook { + // 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. + let input = input + .into_owned_data_buffer() + .context("unable to convert input to loaded data buffer")?; + + // Constructs a LoadImageSource from the input. + let source = LoadImageSource::FromBuffer { + buffer: input.buffer().context("unable to get buffer from input")?, + file_path: input.file_path(), + }; + + // Loads the image using Boot Services LoadImage function. + 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(()) + } +} diff --git a/src/integrations/shim/hook.rs b/src/integrations/shim/hook.rs new file mode 100644 index 0000000..568835c --- /dev/null +++ b/src/integrations/shim/hook.rs @@ -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>> = + 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 { + // 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::(hook_arch) + .context("unable to open security arch protocol")?; + + // Open the security arch2 protocol. + let mut arch_protocol2 = + uefi::boot::open_protocol_exclusive::(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::(hook_arch) + .context("unable to open security arch protocol")?; + + // Open the security arch2 protocol. + let mut arch_protocol2 = + uefi::boot::open_protocol_exclusive::(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(()) + } +} diff --git a/src/main.rs b/src/main.rs index 0aa27ad..fd36399 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,7 @@ use crate::platform::timer::PlatformTimer; use crate::secure::SecureBoot; use crate::utils::PartitionGuidForm; use anyhow::{Context, Result, bail}; -use log::{error, info}; +use log::{error, info, warn}; use std::collections::BTreeMap; use std::ops::Deref; use std::time::Duration; @@ -72,9 +72,9 @@ pub mod utils; /// Run Sprout, returning an error if one occurs. 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")? { - bail!("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. diff --git a/src/utils.rs b/src/utils.rs index 526d61c..b65a240 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use std::ops::Deref; +use uefi::boot::SearchType; use uefi::fs::{FileSystem, Path}; use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly}; use uefi::proto::device_path::{DevicePath, PoolDevicePath}; @@ -103,10 +104,24 @@ pub struct ResolvedPath { pub filesystem_handle: Handle, } +impl ResolvedPath { + /// Read the file specified by this path into a buffer and return it. + pub fn read_file(&self) -> Result> { + let fs = uefi::boot::open_protocol_exclusive::(self.filesystem_handle) + .context("unable to open filesystem protocol")?; + let mut fs = FileSystem::new(fs); + let path = self + .sub_path + .to_string(DisplayOnly(false), AllowShortcuts(false))?; + let content = fs.read(Path::new(&path)); + content.context("unable to read file contents") + } +} + /// 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. /// Returns [ResolvedPath] which contains the resolved components. -pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result { +pub fn resolve_path(default_root_path: Option<&DevicePath>, input: &str) -> Result { let mut path = text_to_device_path(input).context("unable to convert text to path")?; let path_has_device = path .node_iter() @@ -122,6 +137,9 @@ pub fn resolve_path(default_root_path: &DevicePath, input: &str) -> Result Result Result> { +pub fn read_file_contents(default_root_path: Option<&DevicePath>, input: &str) -> Result> { let resolved = resolve_path(default_root_path, input)?; - let fs = uefi::boot::open_protocol_exclusive::(resolved.filesystem_handle) - .context("unable to open filesystem protocol")?; - let mut fs = FileSystem::new(fs); - let path = resolved - .sub_path - .to_string(DisplayOnly(false), AllowShortcuts(false))?; - let content = fs.read(Path::new(&path)); - content.context("unable to read file contents") + resolved.read_file() } /// Filter a string-like Option `input` such that an empty string is [None]. @@ -232,3 +243,25 @@ pub fn partition_guid(path: &DevicePath, form: PartitionGuidForm) -> Result Result> { + // Locate the requested protocol handle. + match uefi::boot::locate_handle_buffer(SearchType::ByProtocol(protocol)) { + // If a handle is found, the protocol is available. + Ok(handles) => Ok(if handles.is_empty() { + None + } else { + Some(handles[0]) + }), + // If an error occurs, check if it is because the protocol is not available. + // If so, return false. Otherwise, return the error. + Err(error) => { + if error.status() == Status::NOT_FOUND { + Ok(None) + } else { + Err(error).context("unable to determine if the protocol is available") + } + } + } +} diff --git a/src/utils/variables.rs b/src/utils/variables.rs index 06f171c..8448be7 100644 --- a/src/utils/variables.rs +++ b/src/utils/variables.rs @@ -90,4 +90,10 @@ impl VariableController { .collect::>(); 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) + } }