2026-03-25 15:56:02 -04:00
|
|
|
use crate::{actions, context::SproutContext};
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::rc::Rc;
|
2026-05-18 12:43:41 +02:00
|
|
|
use alloc::vec::Vec;
|
2025-11-03 02:04:21 -05:00
|
|
|
use alloc::{format, vec};
|
2026-09-26 19:45:47 -07:00
|
|
|
use anyhow::{Context, Result, bail};
|
2025-11-02 23:28:31 -05:00
|
|
|
use edera_sprout_config::actions::chainload::ChainloadConfiguration;
|
|
|
|
|
use edera_sprout_config::actions::edera::EderaConfiguration;
|
2026-09-26 20:19:25 -07:00
|
|
|
use edera_sprout_parsing::{append_initrd, build_xen_config, combine_options, empty_is_none};
|
2025-11-03 20:47:21 -05:00
|
|
|
use eficore::media_loader::{
|
|
|
|
|
MediaLoaderHandle,
|
|
|
|
|
constants::xen::{
|
|
|
|
|
XEN_EFI_CONFIG_MEDIA_GUID, XEN_EFI_KERNEL_MEDIA_GUID, XEN_EFI_RAMDISK_MEDIA_GUID,
|
|
|
|
|
},
|
|
|
|
|
};
|
2026-05-18 12:43:41 +02:00
|
|
|
use eficore::platform::tpm::PlatformTpm;
|
2025-11-02 23:28:31 -05:00
|
|
|
use uefi::Guid;
|
2025-10-14 22:36:22 -07:00
|
|
|
|
2026-05-18 12:43:41 +02:00
|
|
|
/// Register a media loader for the provided `bytes` with the vendor `guid`.
|
2025-10-20 00:06:46 -07:00
|
|
|
/// `what` should indicate some identifying value for error messages
|
|
|
|
|
/// like `config` or `kernel`.
|
|
|
|
|
/// Provides a [MediaLoaderHandle] that can be used to unregister the media loader.
|
2026-05-18 12:43:41 +02:00
|
|
|
fn register_media_loader_bytes(
|
2025-10-14 22:36:22 -07:00
|
|
|
guid: Guid,
|
|
|
|
|
what: &str,
|
2026-05-18 12:43:41 +02:00
|
|
|
bytes: Vec<u8>,
|
2025-10-14 22:36:22 -07:00
|
|
|
) -> Result<MediaLoaderHandle> {
|
2026-05-18 12:43:41 +02:00
|
|
|
MediaLoaderHandle::register(guid, bytes.into_boxed_slice())
|
|
|
|
|
.context(format!("unable to register {} media loader", what))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Read the contents of the loader payload at `path` relative to the sprout image.
|
|
|
|
|
/// `what` should indicate some identifying value for error messages
|
|
|
|
|
/// like `kernel` or `initrd`.
|
|
|
|
|
fn read_loader_payload(context: &Rc<SproutContext>, what: &str, path: &str) -> Result<Vec<u8>> {
|
2025-10-14 22:36:22 -07:00
|
|
|
let path = context.stamp(path);
|
2026-05-18 12:43:41 +02:00
|
|
|
eficore::path::read_file_contents(Some(context.root().loaded_image_path()?), &path)
|
|
|
|
|
.context(format!("unable to read {} file", what))
|
2025-10-14 22:36:22 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
/// Executes the edera action which will boot the Edera hypervisor with the specified
|
|
|
|
|
/// `configuration` and `context`. This action uses Edera-specific Xen EFI stub functionality.
|
2025-10-14 22:36:22 -07:00
|
|
|
pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> Result<()> {
|
2026-09-26 19:45:47 -07:00
|
|
|
// The initrd can be provided as either a single initrd or a chain, but not both.
|
|
|
|
|
if configuration.initrd.is_some() && !configuration.initrd_chain.is_empty() {
|
|
|
|
|
bail!("initrd and initrd-chain cannot be used together");
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 12:43:41 +02:00
|
|
|
// Only register the initrd media loader if the user actually configured one.
|
|
|
|
|
let xen_opts = combine_options(context.stamp_iter(configuration.xen_options.iter()));
|
|
|
|
|
let dom0_args = combine_options(context.stamp_iter(configuration.kernel_options.iter()));
|
2025-10-20 00:06:46 -07:00
|
|
|
|
2026-05-18 12:43:41 +02:00
|
|
|
// Measure xen options and dom0 cmdline into PCR 12 in a fixed order.
|
|
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_KERNEL_PARAMETERS,
|
|
|
|
|
xen_opts.as_bytes(),
|
|
|
|
|
"sprout: xen options",
|
2025-10-18 23:26:05 -07:00
|
|
|
)
|
2026-05-18 12:43:41 +02:00
|
|
|
.context("unable to measure xen options into the TPM")?;
|
|
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_KERNEL_PARAMETERS,
|
|
|
|
|
dom0_args.as_bytes(),
|
|
|
|
|
"sprout: dom0 cmdline",
|
|
|
|
|
)
|
|
|
|
|
.context("unable to measure dom0 cmdline into the TPM")?;
|
|
|
|
|
|
|
|
|
|
// Build the Xen config text and register it as the config media loader. The
|
|
|
|
|
// assembled text is a derived artifact, intentionally not measured.
|
|
|
|
|
let config_handle = register_media_loader_bytes(
|
|
|
|
|
XEN_EFI_CONFIG_MEDIA_GUID,
|
|
|
|
|
"config",
|
|
|
|
|
build_xen_config(&xen_opts, &dom0_args).into_bytes(),
|
|
|
|
|
)
|
|
|
|
|
.context("unable to register config media loader")?;
|
|
|
|
|
|
|
|
|
|
// Read the dom0 kernel, measure it into PCR 11, then register the kernel
|
|
|
|
|
// media loader.
|
|
|
|
|
let kernel_bytes = read_loader_payload(&context, "kernel", &configuration.kernel)?;
|
|
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_KERNEL,
|
|
|
|
|
&kernel_bytes,
|
|
|
|
|
"sprout: dom0 kernel",
|
|
|
|
|
)
|
|
|
|
|
.context("unable to measure dom0 kernel into the TPM")?;
|
|
|
|
|
let kernel_handle =
|
|
|
|
|
register_media_loader_bytes(XEN_EFI_KERNEL_MEDIA_GUID, "kernel", kernel_bytes)
|
|
|
|
|
.context("unable to register kernel media loader")?;
|
|
|
|
|
|
2026-09-26 19:45:47 -07:00
|
|
|
// Read the initrd, or each initrd in the chain concatenated in order, then
|
|
|
|
|
// extend PCR 9 with the combined bytes (empty when no initrd is configured).
|
|
|
|
|
let mut initrd_bytes = Vec::new();
|
|
|
|
|
for p in configuration
|
|
|
|
|
.initrd
|
|
|
|
|
.iter()
|
|
|
|
|
.chain(configuration.initrd_chain.iter())
|
|
|
|
|
{
|
|
|
|
|
if let Some(p) = empty_is_none(Some(p)) {
|
2026-09-26 20:19:25 -07:00
|
|
|
append_initrd(
|
|
|
|
|
&mut initrd_bytes,
|
|
|
|
|
&read_loader_payload(&context, "initrd", p)?,
|
|
|
|
|
);
|
2026-09-26 19:45:47 -07:00
|
|
|
}
|
|
|
|
|
}
|
2026-05-18 12:43:41 +02:00
|
|
|
PlatformTpm::log_event(
|
|
|
|
|
PlatformTpm::PCR_INITRD,
|
|
|
|
|
&initrd_bytes,
|
|
|
|
|
"sprout: dom0 initrd",
|
|
|
|
|
)
|
|
|
|
|
.context("unable to measure dom0 initrd into the TPM")?;
|
2025-10-14 22:36:22 -07:00
|
|
|
|
2025-12-20 22:01:04 -08:00
|
|
|
// Create a vector of media loaders to drop them only after this function completes.
|
2026-05-18 12:43:41 +02:00
|
|
|
let mut media_loaders = vec![config_handle, kernel_handle];
|
2025-10-14 22:36:22 -07:00
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Register the initrd if it is provided.
|
2026-05-18 12:43:41 +02:00
|
|
|
if !initrd_bytes.is_empty() {
|
|
|
|
|
let initrd_handle =
|
|
|
|
|
register_media_loader_bytes(XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd_bytes)
|
2025-10-18 23:26:05 -07:00
|
|
|
.context("unable to register initrd media loader")?;
|
2026-05-18 12:43:41 +02:00
|
|
|
media_loaders.push(initrd_handle);
|
2025-10-14 22:36:22 -07:00
|
|
|
}
|
|
|
|
|
|
2025-10-20 00:06:46 -07:00
|
|
|
// Chainload to the Xen EFI stub.
|
2025-10-14 22:36:22 -07:00
|
|
|
let result = actions::chainload::chainload(
|
|
|
|
|
context.clone(),
|
|
|
|
|
&ChainloadConfiguration {
|
|
|
|
|
path: configuration.xen.clone(),
|
|
|
|
|
options: vec![],
|
|
|
|
|
linux_initrd: None,
|
2026-09-26 19:45:47 -07:00
|
|
|
linux_initrd_chain: vec![],
|
2025-10-14 22:36:22 -07:00
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.context("unable to chainload to xen");
|
|
|
|
|
|
2025-12-20 22:01:04 -08:00
|
|
|
// Explicitly drop the media loaders to clarify when they should be unregistered.
|
|
|
|
|
drop(media_loaders);
|
2025-10-14 22:36:22 -07:00
|
|
|
|
|
|
|
|
result
|
|
|
|
|
}
|