fix(options): stamp initrd and combine options safely by ignoring empty strings

This commit is contained in:
2025-10-27 17:44:30 -04:00
parent a76f9770dc
commit 26315fb4c4
3 changed files with 44 additions and 20 deletions

View File

@@ -53,13 +53,9 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image) let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::<LoadedImage>(image)
.context("unable to open loaded image protocol")?; .context("unable to open loaded image protocol")?;
// Stamp and concatenate the options to pass to the image. // Stamp and combine the options to pass to the image.
let options = configuration let options =
.options utils::combine_options(configuration.options.iter().map(|item| context.stamp(item)));
.iter()
.map(|item| context.stamp(item))
.collect::<Vec<_>>()
.join(" ");
// Pass the options to the image, if any are provided. // Pass the options to the image, if any are provided.
// The holder must drop at the end of this function to ensure the options are not leaked, // The holder must drop at the end of this function to ensure the options are not leaked,
@@ -90,16 +86,14 @@ pub fn chainload(context: Rc<SproutContext>, configuration: &ChainloadConfigurat
} }
// The initrd can be None or empty, so we need to collapse that into a single Option. // The initrd can be None or empty, so we need to collapse that into a single Option.
let initrd = configuration let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref());
.linux_initrd
.as_ref()
.map(|path| context.stamp(path))
.and_then(|path| if path.is_empty() { None } else { Some(path) });
// 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(ref linux_initrd) = initrd { if let Some(linux_initrd) = initrd {
let content = utils::read_file_contents(context.root().loaded_image_path()?, linux_initrd) // Stamp the path to the initrd.
let linux_initrd = context.stamp(linux_initrd);
let content = utils::read_file_contents(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

@@ -40,7 +40,23 @@ pub struct EderaConfiguration {
} }
/// Builds a configuration string for the Xen EFI stub using the specified `configuration`. /// Builds a configuration string for the Xen EFI stub using the specified `configuration`.
fn build_xen_config(configuration: &EderaConfiguration) -> String { fn build_xen_config(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> String {
// Stamp xen options and combine them.
let xen_options = utils::combine_options(
configuration
.xen_options
.iter()
.map(|item| context.stamp(item)),
);
// Stamp kernel options and combine them.
let kernel_options = utils::combine_options(
configuration
.kernel_options
.iter()
.map(|item| context.stamp(item)),
);
// xen config file format is ini-like // xen config file format is ini-like
[ [
// global section // global section
@@ -50,10 +66,10 @@ fn build_xen_config(configuration: &EderaConfiguration) -> String {
// configuration section for sprout // configuration section for sprout
"[sprout]".to_string(), "[sprout]".to_string(),
// xen options // xen options
format!("options={}", configuration.xen_options.join(" ")), format!("options={}", xen_options),
// kernel options, stub replaces the kernel path // kernel options, stub replaces the kernel path
// the kernel is provided via media loader // the kernel is provided via media loader
format!("kernel=stub {}", configuration.kernel_options.join(" ")), format!("kernel=stub {}", kernel_options),
// required or else the last line will be ignored // required or else the last line will be ignored
"".to_string(), "".to_string(),
] ]
@@ -94,7 +110,7 @@ fn register_media_loader_file(
/// `configuration` and `context`. This action uses Edera-specific Xen EFI stub functionality. /// `configuration` and `context`. This action uses Edera-specific Xen EFI stub functionality.
pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> Result<()> { pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) -> Result<()> {
// Build the Xen config file content for this configuration. // Build the Xen config file content for this configuration.
let config = build_xen_config(configuration); let config = build_xen_config(context.clone(), configuration);
// Register the media loader for the config. // Register the media loader for the config.
let config = register_media_loader_text(XEN_EFI_CONFIG_MEDIA_GUID, "config", config) let config = register_media_loader_text(XEN_EFI_CONFIG_MEDIA_GUID, "config", config)
@@ -113,7 +129,7 @@ pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) ->
let mut media_loaders = vec![config, kernel]; let mut media_loaders = vec![config, kernel];
// Register the initrd if it is provided. // Register the initrd if it is provided.
if let Some(ref initrd) = configuration.initrd { if let Some(initrd) = utils::empty_is_none(configuration.initrd.as_ref()) {
let initrd = let initrd =
register_media_loader_file(&context, XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd) register_media_loader_file(&context, XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd)
.context("unable to register initrd media loader")?; .context("unable to register initrd media loader")?;
@@ -131,7 +147,7 @@ pub fn edera(context: Rc<SproutContext>, configuration: &EderaConfiguration) ->
) )
.context("unable to chainload to xen"); .context("unable to chainload to xen");
// Unregister the media loaders on error. // Unregister the media loaders when an error happens.
for media_loader in media_loaders { for media_loader in media_loaders {
if let Err(error) = media_loader.unregister() { if let Err(error) = media_loader.unregister() {
error!("unable to unregister media loader: {}", error); error!("unable to unregister media loader: {}", error);

View File

@@ -161,3 +161,17 @@ pub fn read_file_contents(default_root_path: &DevicePath, input: &str) -> Result
let content = fs.read(Path::new(&path)); let content = fs.read(Path::new(&path));
content.context("unable to read file contents") content.context("unable to read file contents")
} }
/// Filter a string-like Option `input` such that an empty string is [None].
pub fn empty_is_none<T: AsRef<str>>(input: Option<T>) -> Option<T> {
input.filter(|input| !input.as_ref().is_empty())
}
/// Combine a sequence of strings into a single string, separated by spaces, ignoring empty strings.
pub fn combine_options<T: AsRef<str>>(options: impl Iterator<Item = T>) -> String {
options
.flat_map(|item| empty_is_none(Some(item)))
.map(|item| item.as_ref().to_string())
.collect::<Vec<_>>()
.join(" ")
}