From 26315fb4c4288a026518d4df71a1030b07c182e7 Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Mon, 27 Oct 2025 17:44:30 -0400 Subject: [PATCH] fix(options): stamp initrd and combine options safely by ignoring empty strings --- src/actions/chainload.rs | 22 ++++++++-------------- src/actions/edera.rs | 28 ++++++++++++++++++++++------ src/utils.rs | 14 ++++++++++++++ 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/src/actions/chainload.rs b/src/actions/chainload.rs index 3daea39..f5466d4 100644 --- a/src/actions/chainload.rs +++ b/src/actions/chainload.rs @@ -53,13 +53,9 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat let mut loaded_image_protocol = uefi::boot::open_protocol_exclusive::(image) .context("unable to open loaded image protocol")?; - // Stamp and concatenate the options to pass to the image. - let options = configuration - .options - .iter() - .map(|item| context.stamp(item)) - .collect::>() - .join(" "); + // Stamp and combine the options to pass to the image. + let options = + utils::combine_options(configuration.options.iter().map(|item| context.stamp(item))); // 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, @@ -90,16 +86,14 @@ pub fn chainload(context: Rc, configuration: &ChainloadConfigurat } // The initrd can be None or empty, so we need to collapse that into a single Option. - let initrd = configuration - .linux_initrd - .as_ref() - .map(|path| context.stamp(path)) - .and_then(|path| if path.is_empty() { None } else { Some(path) }); + let initrd = utils::empty_is_none(configuration.linux_initrd.as_ref()); // If an initrd is provided, register it with the EFI stack. let mut initrd_handle = None; - if let Some(ref linux_initrd) = initrd { - let content = utils::read_file_contents(context.root().loaded_image_path()?, linux_initrd) + if let Some(linux_initrd) = 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")?; let handle = MediaLoaderHandle::register(LINUX_EFI_INITRD_MEDIA_GUID, content.into_boxed_slice()) diff --git a/src/actions/edera.rs b/src/actions/edera.rs index e9cf1d5..b5a8e1f 100644 --- a/src/actions/edera.rs +++ b/src/actions/edera.rs @@ -40,7 +40,23 @@ pub struct EderaConfiguration { } /// 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, 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 [ // global section @@ -50,10 +66,10 @@ fn build_xen_config(configuration: &EderaConfiguration) -> String { // configuration section for sprout "[sprout]".to_string(), // xen options - format!("options={}", configuration.xen_options.join(" ")), + format!("options={}", xen_options), // kernel options, stub replaces the kernel path // 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 "".to_string(), ] @@ -94,7 +110,7 @@ fn register_media_loader_file( /// `configuration` and `context`. This action uses Edera-specific Xen EFI stub functionality. pub fn edera(context: Rc, configuration: &EderaConfiguration) -> Result<()> { // 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. let config = register_media_loader_text(XEN_EFI_CONFIG_MEDIA_GUID, "config", config) @@ -113,7 +129,7 @@ pub fn edera(context: Rc, configuration: &EderaConfiguration) -> let mut media_loaders = vec![config, kernel]; // 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 = register_media_loader_file(&context, XEN_EFI_RAMDISK_MEDIA_GUID, "initrd", initrd) .context("unable to register initrd media loader")?; @@ -131,7 +147,7 @@ pub fn edera(context: Rc, configuration: &EderaConfiguration) -> ) .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 { if let Err(error) = media_loader.unregister() { error!("unable to unregister media loader: {}", error); diff --git a/src/utils.rs b/src/utils.rs index 1179df9..c40ead1 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -161,3 +161,17 @@ pub fn read_file_contents(default_root_path: &DevicePath, input: &str) -> Result let content = fs.read(Path::new(&path)); 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>(input: Option) -> Option { + 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>(options: impl Iterator) -> String { + options + .flat_map(|item| empty_is_none(Some(item))) + .map(|item| item.as_ref().to_string()) + .collect::>() + .join(" ") +}