5 Commits

54 changed files with 322 additions and 205 deletions

View File

@@ -48,28 +48,15 @@ jobs:
- name: 'assemble artifacts'
run: ./hack/assemble.sh
- name: 'upload sprout-x86_64.efi.zip artifact'
id: upload-sprout-x86_64-efi
- name: 'upload artifacts'
id: upload
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: sprout-x86_64.efi.zip
path: target/assemble/sprout-x86_64.efi
name: artifacts
path: target/assemble/*
- name: 'upload sprout-aarch64.efi.zip artifact'
id: upload-sprout-aarch64-efi
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: sprout-aarch64.efi.zip
path: target/assemble/sprout-aarch64.efi
- name: 'attest sprout-x86_64.efi.zip artifact'
- name: 'attest artifacts'
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-name: sprout-x86_64.efi.zip
subject-digest: "sha256:${{ steps.upload-sprout-x86_64-efi.outputs.artifact-digest }}"
- name: 'attest sprout-aarch64.efi.zip artifact'
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-name: sprout-aarch64.efi.zip
subject-digest: "sha256:${{ steps.upload-sprout-aarch64-efi.outputs.artifact-digest }}"
subject-name: artifacts.zip
subject-digest: "sha256:${{ steps.upload.outputs.artifact-digest }}"

66
Cargo.lock generated
View File

@@ -66,7 +66,7 @@ dependencies = [
[[package]]
name = "edera-sprout"
version = "0.0.20"
version = "0.0.21"
dependencies = [
"anyhow",
"bitflags",
@@ -74,6 +74,8 @@ dependencies = [
"hex",
"log",
"sha2",
"shlex",
"spin",
"toml",
"uefi",
"uefi-raw",
@@ -81,17 +83,11 @@ dependencies = [
[[package]]
name = "edera-sprout-config"
version = "0.0.20"
version = "0.0.21"
dependencies = [
"serde",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "generic-array"
version = "0.14.9"
@@ -102,34 +98,27 @@ dependencies = [
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "indexmap"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.28"
@@ -174,6 +163,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.228"
@@ -224,6 +219,21 @@ dependencies = [
"digest",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "spin"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
dependencies = [
"lock_api",
]
[[package]]
name = "syn"
version = "2.0.108"
@@ -241,12 +251,10 @@ version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
dependencies = [
"indexmap",
"serde_core",
"serde_spanned",
"toml_datetime",
"toml_parser",
"toml_writer",
"winnow",
]
@@ -268,12 +276,6 @@ dependencies = [
"winnow",
]
[[package]]
name = "toml_writer"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
[[package]]
name = "typenum"
version = "1.19.0"

View File

@@ -7,22 +7,45 @@ resolver = "3"
[workspace.package]
license = "Apache-2.0"
version = "0.0.20"
version = "0.0.21"
homepage = "https://sprout.edera.dev"
repository = "https://github.com/edera-dev/sprout"
edition = "2024"
[workspace.dependencies]
anyhow = "1.0.100"
bitflags = "2.10.0"
hex = "0.4.3"
log = "0.4.28"
serde = "1.0.228"
sha2 = "0.10.9"
toml = "0.9.8"
spin = "0.10.0"
uefi = "0.36.0"
uefi-raw = "0.12.0"
[workspace.dependencies.anyhow]
version = "1.0.100"
default-features = false
[workspace.dependencies.hex]
version = "0.4.3"
default-features = false
features = ["alloc"]
[workspace.dependencies.serde]
version = "1.0.228"
default-features = false
features = ["alloc", "derive"]
[workspace.dependencies.sha2]
version = "0.10.9"
default-features = false
[workspace.dependencies.shlex]
version = "1.3.0"
default-features = false
[workspace.dependencies.toml]
version = "0.9.8"
default-features = false
features = ["serde", "parse"]
# Common build profiles
# NOTE: We have to compile everything for opt-level = 2 due to optimization passes
# which don't handle the UEFI target properly.

View File

@@ -5,11 +5,7 @@ This guide is a work in progress.
## Development Setup
You can use any Rust development environment to develop Sprout.
Rustup is recommended as the Rust toolchain manager to manage Rust versions and targets.
Sprout currently requires Rust nightly to support uefi_std. See [uefi_std](https://doc.rust-lang.org/beta/rustc/platform-support/unknown-uefi.html) for more details.
We currently only support `x86_64-unknown-uefi` and `aarch64-unknown-uefi` targets.
To test your changes in QEMU, please run `./hack/dev/boot.sh`, you can specify `x86_64` or `aarch64`

View File

@@ -9,7 +9,7 @@ edition.workspace = true
[dependencies.serde]
workspace = true
features = ["derive"]
default-features = false
[lib]
name = "edera_sprout_config"

View File

@@ -1,3 +1,5 @@
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
/// The configuration of the chainload action.

View File

@@ -1,3 +1,5 @@
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
/// The configuration of the edera action which boots the Edera hypervisor.

View File

@@ -1,3 +1,4 @@
use alloc::string::String;
use serde::{Deserialize, Serialize};
/// The configuration of the print action.

View File

@@ -1,3 +1,4 @@
use alloc::string::String;
use serde::{Deserialize, Serialize};
/// Declares a driver configuration.

View File

@@ -1,5 +1,7 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Declares a boot entry to display in the boot menu.
///

View File

@@ -1,3 +1,4 @@
use alloc::string::String;
use serde::{Deserialize, Serialize};
/// The filesystem device match extractor.

View File

@@ -1,4 +1,5 @@
use crate::entries::EntryDeclaration;
use alloc::string::{String, ToString};
use serde::{Deserialize, Serialize};
/// The default path to the BLS directory.

View File

@@ -1,6 +1,8 @@
use crate::entries::EntryDeclaration;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// List generator configuration.
/// The list generator produces multiple entries based

View File

@@ -1,6 +1,8 @@
use crate::entries::EntryDeclaration;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Matrix generator configuration.
/// The matrix generator produces multiple entries based

View File

@@ -1,5 +1,7 @@
//! Sprout configuration descriptions.
//! This crate provides all the configuration structures for Sprout.
#![no_std]
extern crate alloc;
use crate::actions::ActionDeclaration;
use crate::drivers::DriverDeclaration;
@@ -7,8 +9,9 @@ use crate::entries::EntryDeclaration;
use crate::extractors::ExtractorDeclaration;
use crate::generators::GeneratorDeclaration;
use crate::phases::PhasesConfiguration;
use alloc::collections::BTreeMap;
use alloc::string::String;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
pub mod actions;
pub mod drivers;

View File

@@ -1,5 +1,7 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// Configures the various phases of the boot process.
/// This allows hooking various phases to run actions.

View File

@@ -13,12 +13,14 @@ bitflags.workspace = true
edera-sprout-config.path = "../config"
hex.workspace = true
sha2.workspace = true
shlex.workspace = true
spin.workspace = true
toml.workspace = true
log.workspace = true
[dependencies.uefi]
workspace = true
features = ["alloc", "logger"]
features = ["alloc", "global_allocator", "logger", "panic_handler"]
[dependencies.uefi-raw]
workspace = true

View File

@@ -1,6 +1,6 @@
use crate::context::SproutContext;
use alloc::rc::Rc;
use anyhow::{Context, Result, bail};
use std::rc::Rc;
/// EFI chainloader action.
pub mod chainload;

View File

@@ -4,10 +4,11 @@ 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;
use alloc::boxed::Box;
use alloc::rc::Rc;
use anyhow::{Context, Result, bail};
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use log::error;
use std::rc::Rc;
use uefi::CString16;
use uefi::proto::loaded_image::LoadedImage;

View File

@@ -1,5 +1,3 @@
use std::rc::Rc;
use crate::{
actions,
context::SproutContext,
@@ -13,6 +11,9 @@ use crate::{
},
},
};
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use alloc::{format, vec};
use anyhow::{Context, Result};
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_config::actions::edera::EderaConfiguration;

View File

@@ -1,8 +1,8 @@
use crate::context::SproutContext;
use alloc::rc::Rc;
use anyhow::Result;
use edera_sprout_config::actions::print::PrintConfiguration;
use log::info;
use std::rc::Rc;
/// Executes the print action with the specified `configuration` inside the provided `context`.
pub fn print(context: Rc<SproutContext>, configuration: &PrintConfiguration) -> Result<()> {

View File

@@ -1,4 +1,6 @@
use crate::utils;
use alloc::string::ToString;
use alloc::{format, vec};
use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;

View File

@@ -1,5 +1,9 @@
use crate::utils;
use crate::utils::vercmp;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use alloc::{format, vec};
use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;
@@ -7,7 +11,6 @@ use edera_sprout_config::actions::chainload::ChainloadConfiguration;
use edera_sprout_config::entries::EntryDeclaration;
use edera_sprout_config::generators::GeneratorDeclaration;
use edera_sprout_config::generators::list::ListConfiguration;
use std::collections::BTreeMap;
use uefi::CString16;
use uefi::fs::{FileSystem, Path, PathBuf};
use uefi::proto::device_path::DevicePath;

View File

@@ -1,4 +1,6 @@
use crate::utils;
use alloc::string::ToString;
use alloc::{format, vec};
use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use edera_sprout_config::actions::ActionDeclaration;

View File

@@ -1,10 +1,11 @@
use crate::options::SproutOptions;
use crate::platform::tpm::PlatformTpm;
use crate::utils;
use alloc::vec::Vec;
use anyhow::{Context, Result, bail};
use core::ops::Deref;
use edera_sprout_config::{RootConfiguration, latest_version};
use log::info;
use std::ops::Deref;
use toml::Value;
use uefi::proto::device_path::LoadedImageDevicePath;

View File

@@ -1,11 +1,15 @@
use crate::options::SproutOptions;
use crate::platform::timer::PlatformTimer;
use alloc::boxed::Box;
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::anyhow;
use anyhow::{Result, bail};
use core::cmp::Reverse;
use edera_sprout_config::actions::ActionDeclaration;
use std::cmp::Reverse;
use std::collections::{BTreeMap, BTreeSet};
use std::rc::Rc;
use uefi::proto::device_path::DevicePath;
/// The maximum number of iterations that can be performed in [SproutContext::finalize].

View File

@@ -1,11 +1,13 @@
use crate::context::SproutContext;
use crate::integrations::shim::{ShimInput, ShimSupport};
use crate::utils;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::String;
use anyhow::{Context, Result};
pub(crate) use edera_sprout_config::drivers::DriverDeclaration;
use edera_sprout_config::drivers::DriverDeclaration;
use log::info;
use std::collections::BTreeMap;
use std::rc::Rc;
use uefi::boot::SearchType;
/// Loads the driver specified by the `driver` declaration.

View File

@@ -1,6 +1,7 @@
use crate::context::SproutContext;
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use edera_sprout_config::entries::EntryDeclaration;
use std::rc::Rc;
/// Represents an entry that is stamped and ready to be booted.
#[derive(Clone)]

View File

@@ -1,7 +1,8 @@
use crate::context::SproutContext;
use alloc::rc::Rc;
use alloc::string::String;
use anyhow::{Result, bail};
use edera_sprout_config::extractors::ExtractorDeclaration;
use std::rc::Rc;
/// The filesystem device match extractor.
pub mod filesystem_device_match;

View File

@@ -1,10 +1,11 @@
use crate::context::SproutContext;
use crate::utils;
use alloc::rc::Rc;
use alloc::string::String;
use anyhow::{Context, Result, anyhow, bail};
use core::ops::Deref;
use core::str::FromStr;
use edera_sprout_config::extractors::filesystem_device_match::FilesystemDeviceMatchExtractor;
use std::ops::Deref;
use std::rc::Rc;
use std::str::FromStr;
use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::DevicePath;
use uefi::proto::media::file::{File, FileSystemVolumeLabel};

View File

@@ -1,9 +1,10 @@
use crate::context::SproutContext;
use crate::entries::BootableEntry;
use alloc::rc::Rc;
use alloc::vec::Vec;
use anyhow::Result;
use anyhow::bail;
use edera_sprout_config::generators::GeneratorDeclaration;
use std::rc::Rc;
/// The BLS generator.
pub mod bls;

View File

@@ -3,11 +3,14 @@ use crate::entries::BootableEntry;
use crate::generators::bls::entry::BlsEntry;
use crate::utils;
use crate::utils::vercmp;
use alloc::format;
use alloc::rc::Rc;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{Context, Result};
use core::cmp::Ordering;
use core::str::FromStr;
use edera_sprout_config::generators::bls::BlsConfiguration;
use std::cmp::Ordering;
use std::rc::Rc;
use std::str::FromStr;
use uefi::cstr16;
use uefi::fs::{FileSystem, PathBuf};
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};

View File

@@ -1,5 +1,6 @@
use alloc::string::{String, ToString};
use anyhow::{Error, Result};
use std::str::FromStr;
use core::str::FromStr;
/// Represents a parsed BLS entry.
/// Fields unrelated to Sprout are not included.

View File

@@ -1,8 +1,10 @@
use crate::context::SproutContext;
use crate::entries::BootableEntry;
use alloc::rc::Rc;
use alloc::string::ToString;
use alloc::vec::Vec;
use anyhow::Result;
use edera_sprout_config::generators::list::ListConfiguration;
use std::rc::Rc;
/// Generates a set of entries using the specified `list` configuration in the `context`.
pub fn generate(

View File

@@ -1,11 +1,14 @@
use crate::context::SproutContext;
use crate::entries::BootableEntry;
use crate::generators::list;
use alloc::collections::BTreeMap;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec;
use alloc::vec::Vec;
use anyhow::Result;
use edera_sprout_config::generators::list::ListConfiguration;
use edera_sprout_config::generators::matrix::MatrixConfiguration;
use std::collections::BTreeMap;
use std::rc::Rc;
/// Builds out multiple generations of `input` based on a matrix style.
/// For example, if input is: {"x": ["a", "b"], "y": ["c", "d"]}

View File

@@ -2,6 +2,9 @@ use crate::integrations::bootloader_interface::bitflags::LoaderFeatures;
use crate::platform::timer::PlatformTimer;
use crate::utils::device_path_subpath;
use crate::utils::variables::{VariableClass, VariableController};
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{Context, Result};
use uefi::proto::device_path::DevicePath;
use uefi::{Guid, guid};

View File

@@ -3,10 +3,13 @@ use crate::secure::SecureBoot;
use crate::utils;
use crate::utils::ResolvedPath;
use crate::utils::variables::{VariableClass, VariableController};
use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec::Vec;
use anyhow::{Context, Result, anyhow, bail};
use core::ffi::c_void;
use core::pin::Pin;
use log::warn;
use std::ffi::c_void;
use std::pin::Pin;
use uefi::Handle;
use uefi::boot::LoadImageSource;
use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};

View File

@@ -1,8 +1,9 @@
use crate::integrations::shim::{ShimInput, ShimSupport, ShimVerificationOutput};
use crate::utils;
use anyhow::{Context, Result, bail};
use anyhow::{Context, Result};
use core::slice;
use log::warn;
use std::sync::{LazyLock, Mutex};
use spin::{Lazy, Mutex};
use uefi::proto::device_path::FfiDevicePath;
use uefi::proto::unsafe_protocol;
use uefi::{Guid, guid};
@@ -45,8 +46,7 @@ struct SecurityHookState {
/// 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));
static GLOBAL_HOOK_STATE: Lazy<Mutex<Option<SecurityHookState>>> = Lazy::new(|| Mutex::new(None));
/// Security hook helper.
pub struct SecurityHook;
@@ -110,24 +110,13 @@ impl SecurityHook {
// Verify the input, if it fails, call the original hook.
if !Self::verify(input) {
// Acquire the global hook state to grab the original hook.
let function = match GLOBAL_HOOK_STATE.lock() {
// We have acquired the lock, so we can find the original hook.
Ok(state) => match state.as_ref() {
// The hook state is available, so we can acquire the original hook.
Some(state) => state.original_hook.file_authentication_state,
let function = match GLOBAL_HOOK_STATE.lock().as_ref() {
// The hook state is available, so we can acquire the original hook.
Some(state) => state.original_hook.file_authentication_state,
// The hook state is not available, so we can't call the original hook.
None => {
warn!("global hook state is not available, unable to call original hook");
return Status::LOAD_ERROR;
}
},
Err(error) => {
warn!(
"unable to acquire global hook state lock to call original hook: {}",
error,
);
// The hook state is not available, so we can't call the original hook.
None => {
warn!("global hook state is not available, unable to call original hook");
return Status::LOAD_ERROR;
}
};
@@ -161,7 +150,7 @@ impl SecurityHook {
}
// Construct a slice out of the file buffer and size.
let buffer = unsafe { std::slice::from_raw_parts(file_buffer, file_size) };
let buffer = unsafe { slice::from_raw_parts(file_buffer, file_size) };
// Construct a shim input from the path.
let input = ShimInput::SecurityHookBuffer(Some(path), buffer);
@@ -169,24 +158,13 @@ impl SecurityHook {
// Verify the input, if it fails, call the original hook.
if !Self::verify(input) {
// Acquire the global hook state to grab the original hook.
let function = match GLOBAL_HOOK_STATE.lock() {
// We have acquired the lock, so we can find the original hook.
Ok(state) => match state.as_ref() {
// The hook state is available, so we can acquire the original hook.
Some(state) => state.original_hook2.file_authentication,
let function = match GLOBAL_HOOK_STATE.lock().as_ref() {
// The hook state is available, so we can acquire the original hook.
Some(state) => state.original_hook2.file_authentication,
// The hook state is not available, so we can't call the original hook.
None => {
warn!("global hook state is not available, unable to call original hook");
return Status::LOAD_ERROR;
}
},
Err(error) => {
warn!(
"unable to acquire global hook state lock to call original hook: {}",
error
);
// The hook state is not available, so we can't call the original hook.
None => {
warn!("global hook state is not available, unable to call original hook");
return Status::LOAD_ERROR;
}
};
@@ -237,9 +215,7 @@ impl SecurityHook {
};
// 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");
};
let mut global_state = GLOBAL_HOOK_STATE.lock();
global_state.replace(state);
// Install the hooks into the UEFI stack.
@@ -276,9 +252,7 @@ impl SecurityHook {
.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");
};
let mut global_state = GLOBAL_HOOK_STATE.lock();
// Take the state and replace the original functions.
let Some(state) = global_state.take() else {

View File

@@ -1,8 +1,8 @@
#![doc = include_str!("../README.md")]
#![feature(uefi_std)]
#![no_std]
#![no_main]
/// The delay to wait for when an error occurs in Sprout.
const DELAY_ON_ERROR: Duration = Duration::from_secs(10);
extern crate alloc;
use crate::context::{RootContext, SproutContext};
use crate::entries::BootableEntry;
@@ -14,13 +14,18 @@ use crate::platform::timer::PlatformTimer;
use crate::platform::tpm::PlatformTpm;
use crate::secure::SecureBoot;
use crate::utils::PartitionGuidForm;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::ToString;
use alloc::vec::Vec;
use anyhow::{Context, Result, bail};
use core::ops::Deref;
use core::time::Duration;
use edera_sprout_config::RootConfiguration;
use log::{error, info, warn};
use std::collections::BTreeMap;
use std::ops::Deref;
use std::time::Duration;
use uefi::entry;
use uefi::proto::device_path::LoadedImageDevicePath;
use uefi_raw::Status;
/// actions: Code that can be configured and executed by Sprout.
pub mod actions;
@@ -73,6 +78,9 @@ pub mod options;
/// utils: Utility functions that are used by other parts of Sprout.
pub mod utils;
/// The delay to wait for when an error occurs in Sprout.
const DELAY_ON_ERROR: Duration = Duration::from_secs(10);
/// Run Sprout, returning an error if one occurs.
fn run() -> Result<()> {
// For safety reasons, we will note that Secure Boot is in beta on Sprout.
@@ -373,9 +381,14 @@ fn run() -> Result<()> {
/// The main entrypoint of sprout.
/// It is possible this function will not return if actions that are executed
/// exit boot services or do not return control to sprout.
fn main() -> Result<()> {
#[entry]
fn efi_main() -> Status {
// Initialize the basic UEFI environment.
setup::init()?;
// If initialization fails, we will return ABORTED.
if let Err(error) = setup::init() {
error!("unable to initialize environment: {}", error);
return Status::ABORTED;
}
// Run Sprout, then handle the error.
let result = run();
@@ -387,9 +400,10 @@ fn main() -> Result<()> {
}
// Sleep to allow the user to read the error.
uefi::boot::stall(DELAY_ON_ERROR);
return Status::ABORTED;
}
// Sprout doesn't necessarily guarantee anything was booted.
// If we reach here, we will exit back to whoever called us.
Ok(())
Status::SUCCESS
}

View File

@@ -1,9 +1,10 @@
use crate::entries::BootableEntry;
use crate::integrations::bootloader_interface::BootloaderInterface;
use crate::platform::timer::PlatformTimer;
use alloc::vec;
use anyhow::{Context, Result, bail};
use core::time::Duration;
use log::{info, warn};
use std::time::Duration;
use uefi::ResultExt;
use uefi::boot::TimerTrigger;
use uefi::proto::console::text::{Input, Key, ScanCode};

View File

@@ -1,6 +1,10 @@
use crate::options::parser::{OptionDescription, OptionForm, OptionsRepresentable};
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use anyhow::{Context, Result, bail};
use std::collections::BTreeMap;
/// Acquire arguments from UEFI environment.
pub mod env;
/// The Sprout options parser.
pub mod parser;

View File

@@ -0,0 +1,77 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{Context, Result, bail};
use uefi::proto::loaded_image::{LoadOptionsError, LoadedImage};
/// Loads the command-line arguments passed to Sprout.
pub fn args() -> Result<Vec<String>> {
// Acquire the image handle of Sprout.
let handle = uefi::boot::image_handle();
// Open the LoadedImage protocol for Sprout.
let loaded_image = uefi::boot::open_protocol_exclusive::<LoadedImage>(handle)
.context("unable to open loaded image protocol for sprout")?;
// Load the command-line argument string.
let options = match loaded_image.load_options_as_cstr16() {
// Load options were passed. We will return them for processing.
Ok(options) => options,
// No load options were passed. We will return an empty vector.
Err(LoadOptionsError::NotSet) => {
return Ok(Vec::new());
}
Err(LoadOptionsError::NotAligned) => {
bail!("load options are not properly aligned");
}
Err(LoadOptionsError::InvalidString(error)) => {
bail!("load options are not a valid string: {}", error);
}
};
// Convert the options to a string.
let options = options.to_string();
// Use shlex to parse the options.
// If shlex fails, we will fall back to a simple whitespace split.
let mut args = shlex::split(&options).unwrap_or_else(|| {
options
.split_ascii_whitespace()
.map(|string| string.to_string())
.collect::<Vec<_>>()
});
// If there is a first argument, check if it is not an option.
// If it is not, we will assume it is the path to the executable and remove it.
if let Some(arg) = args.first()
&& !arg.starts_with('-')
{
args.remove(0);
}
// Correct firmware that may add invalid arguments at the start.
// Witnessed this on a Dell Precision 5690 when direct booting.
loop {
// Grab the first argument or break.
let Some(arg) = args.first() else {
break;
};
// Check if the argument is a valid character.
// If it is not, remove it and continue.
let Some(first_character) = arg.chars().next() else {
break;
};
// If the character is not a printable character or a backtick, remove it and continue.
if first_character < 0x1f as char || first_character == '`' {
args.remove(0);
continue;
}
break;
}
Ok(args)
}

View File

@@ -1,6 +1,10 @@
use crate::options::env;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use anyhow::{Context, Result, bail};
use core::ptr::null_mut;
use log::info;
use std::collections::BTreeMap;
use uefi_raw::Status;
/// The type of option. This disambiguates different behavior
/// of how options are handled.
@@ -47,23 +51,7 @@ pub trait OptionsRepresentable {
// Collect all the arguments to Sprout.
// Skip the first argument, which is the path to our executable.
let mut args = std::env::args().skip(1).collect::<Vec<_>>();
// Correct firmware that may add invalid arguments at the start.
// Witnessed this on a Dell Precision 5690 when direct booting.
loop {
// Grab the first argument or break.
let Some(arg) = args.first() else {
break;
};
// If the argument starts with a tilde, remove it.
if arg.starts_with("`") {
args.remove(0);
continue;
}
break;
}
let args = env::args()?;
// Represent options as key-value pairs.
let mut options = BTreeMap::new();
@@ -77,7 +65,7 @@ pub trait OptionsRepresentable {
break;
};
// If the doesn't start with --, that is invalid.
// If the option doesn't start with --, that is invalid.
if !option.starts_with("--") {
bail!("invalid option: {option}");
}
@@ -144,7 +132,9 @@ pub trait OptionsRepresentable {
);
}
// Exit because the help has been displayed.
std::process::exit(0);
unsafe {
uefi::boot::exit(uefi::boot::image_handle(), Status::SUCCESS, 0, null_mut());
};
}
// Insert the option and the value into the map.

View File

@@ -1,8 +1,9 @@
use crate::actions;
use crate::context::SproutContext;
use alloc::format;
use alloc::rc::Rc;
use anyhow::{Context, Result};
use edera_sprout_config::phases::PhaseConfiguration;
use std::rc::Rc;
/// Executes the specified [phase] of the boot process.
/// The value [phase] should be a reference of a specific phase in the [PhasesConfiguration].

View File

@@ -1,7 +1,7 @@
// Referenced https://github.com/sheroz/tick_counter (MIT license) as a baseline.
// Architecturally modified to support UEFI and remove x86 (32-bit) support.
use std::time::Duration;
use core::time::Duration;
/// Support for aarch64 timers.
#[cfg(target_arch = "aarch64")]

View File

@@ -1,5 +1,5 @@
use crate::platform::timer::TickFrequency;
use std::arch::asm;
use core::arch::asm;
/// Reads the cntvct_el0 counter and returns the value.
pub fn ticks() -> u64 {

View File

@@ -1,6 +1,6 @@
use crate::platform::timer::TickFrequency;
use core::arch::asm;
use std::time::Duration;
use core::time::Duration;
/// We will measure the frequency of the timer based on 1000 microseconds.
/// This will result in a call to BS->Stall(1000) in the end.

View File

@@ -1,27 +1,8 @@
use anyhow::{Context, Result};
use std::os::uefi as uefi_std;
/// Initializes the UEFI environment.
///
/// This fetches the system table and current image handle from uefi_std and injects
/// them into the uefi crate.
pub fn init() -> Result<()> {
// Acquire the system table and image handle from the uefi_std environment.
let system_table = uefi_std::env::system_table();
let image_handle = uefi_std::env::image_handle();
// SAFETY: The UEFI variables above come from the Rust std.
// These variables are not-null and calling the uefi crates with these values is validated
// to be corrected by hand.
unsafe {
// Set the system table and image handle.
uefi::table::set_system_table(system_table.as_ptr().cast());
let handle = uefi::Handle::from_ptr(image_handle.as_ptr().cast())
.context("unable to resolve image handle")?;
uefi::boot::set_image_handle(handle);
}
// Initialize the uefi logger mechanism and other helpers.
// Initialize the uefi internals.
uefi::helpers::init().context("unable to initialize uefi")?;
Ok(())
}

View File

@@ -1,6 +1,10 @@
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{Context, Result, bail};
use core::ops::Deref;
use sha2::{Digest, Sha256};
use std::ops::Deref;
use uefi::boot::SearchType;
use uefi::fs::{FileSystem, Path};
use uefi::proto::device_path::text::{AllowShortcuts, DevicePathFromText, DisplayOnly};

View File

@@ -1,3 +1,5 @@
use alloc::vec;
use alloc::vec::Vec;
use anyhow::{Context, Result};
use uefi::proto::console::gop::{BltOp, BltPixel, BltRegion, GraphicsOutput};

View File

@@ -1,5 +1,8 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use anyhow::{Context, Result, bail};
use std::ffi::c_void;
use core::ffi::c_void;
use core::ptr;
use uefi::proto::device_path::DevicePath;
use uefi::proto::device_path::build::DevicePathBuilder;
use uefi::proto::device_path::build::media::Vendor;
@@ -261,8 +264,7 @@ impl MediaLoaderHandle {
let protocol = Box::from_raw(self.protocol);
// Retrieve a box for the data we passed in.
let slice =
std::ptr::slice_from_raw_parts_mut(protocol.address as *mut u8, protocol.length);
let slice = ptr::slice_from_raw_parts_mut(protocol.address as *mut u8, protocol.length);
let data = Box::from_raw(slice);
// Drop all the allocations explicitly, as we don't want to leak them.

View File

@@ -1,4 +1,7 @@
use crate::utils;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use anyhow::{Context, Result};
use log::warn;
use uefi::{CString16, guid};

View File

@@ -1,5 +1,5 @@
use std::cmp::Ordering;
use std::iter::Peekable;
use core::cmp::Ordering;
use core::iter::Peekable;
/// Handles single character advancement and comparison.
macro_rules! handle_single_char {

View File

@@ -1,4 +1,4 @@
[toolchain]
channel = "nightly"
components = ["rustfmt", "rust-std", "clippy"]
channel = "1.91.0"
components = ["rustfmt", "clippy"]
targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"]