From 8b94ebe5bd2078dc0e1930237d4f652a77285fac Mon Sep 17 00:00:00 2001 From: Alex Zenla Date: Sat, 26 Sep 2026 20:07:58 -0700 Subject: [PATCH] fix(env): decode load options that aren't a clean UCS-2 string Load options were decoded with CStr16::from_u16_with_nul, which needs an even byte count and exactly one null at the very end. Firmware that passed an odd size, left off the null terminator, or padded the string with extra nulls made argument parsing fail, and Sprout refused to boot. Decode the raw bytes as little-endian UTF-16 up to the first null instead, replacing anything that isn't valid. --- crates/eficore/src/env.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/crates/eficore/src/env.rs b/crates/eficore/src/env.rs index f054f60..48c26d1 100644 --- a/crates/eficore/src/env.rs +++ b/crates/eficore/src/env.rs @@ -1,7 +1,7 @@ use alloc::string::{String, ToString}; use alloc::vec::Vec; -use anyhow::{Context, Result, bail}; -use uefi::proto::loaded_image::{LoadOptionsError, LoadedImage}; +use anyhow::{Context, Result}; +use uefi::proto::loaded_image::LoadedImage; /// Loads the command-line arguments passed to the current image. pub fn args() -> Result> { @@ -13,26 +13,25 @@ pub fn args() -> Result> { .context("unable to open loaded image protocol for current image")?; // 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, - + // Load options are usually a null-terminated UCS-2 string, but firmware is not consistent + // about this. Some firmware passes an odd number of bytes, leaves off the null terminator, + // or pads the string with extra nulls. We decode everything up to the first null and + // replace anything that isn't valid UCS-2, instead of refusing to boot. + let Some(options) = loaded_image.load_options_as_bytes() else { // 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); - } + return Ok(Vec::new()); }; - // Convert the options to a string. - let options = options.to_string(); + // Decode the options as little-endian UTF-16, stopping at the first null. + // A trailing odd byte can't be part of a character, so it is dropped. + let options = options + .as_chunks::<2>() + .0 + .iter() + .map(|pair| u16::from_le_bytes(*pair)) + .take_while(|c| *c != 0) + .collect::>(); + let options = String::from_utf16_lossy(&options); // Use shlex to parse the options. // If shlex fails, we will perform a simple whitespace split.