fix(variables): accept UTF-16 values without a NUL terminator

EFI string variables written without a trailing NUL, or with extra NUL
padding, failed to decode and were silently treated as unset. Decode up
to the first NUL and terminate the string ourselves, as systemd-boot
does.

Because empty values now decode to an empty string rather than failing,
treat empty LoaderEntryDefault and LoaderEntryOneShot values as unset so
an empty one-shot entry doesn't fail to match any entry.
This commit is contained in:
alex authored and Alex Zenla committed 2026-09-26 18:58:54 -07:00
1 parent df5d8f5c39
commit ad37a2bcf3
2 files changed
+13 -4

No files matched your search

+6 -3
View File
@@ -295,9 +295,11 @@ impl BootloaderInterface {
/// Get the default entry set by the bootloader interface. /// Get the default entry set by the bootloader interface.
pub fn get_default_entry() -> Result<Option<String>> { pub fn get_default_entry() -> Result<Option<String>> {
Self::VENDOR // An empty value is treated as if the default entry was not set.
Ok(Self::VENDOR
.get_cstr16("LoaderEntryDefault") .get_cstr16("LoaderEntryDefault")
.context("unable to get default entry from bootloader interface") .context("unable to get default entry from bootloader interface")?
.filter(|value| !value.is_empty()))
} }
/// Get the oneshot entry set by the bootloader interface. /// Get the oneshot entry set by the bootloader interface.
@@ -318,6 +320,7 @@ impl BootloaderInterface {
.context("unable to remove oneshot entry")?; .context("unable to remove oneshot entry")?;
// Return the oneshot value. // Return the oneshot value.
Ok(Some(value)) // An empty value is treated as if the oneshot entry was not set.
Ok(Some(value).filter(|value| !value.is_empty()))
} }
} }
+7 -1
View File
@@ -10,15 +10,21 @@ pub fn utf16_bytes_to_cstring16(bytes: &[u8]) -> Result<CString16> {
} }
// Convert the bytes to UTF-16 data. // Convert the bytes to UTF-16 data.
let data = bytes let mut data = bytes
// Chunk everything into two bytes. // Chunk everything into two bytes.
.as_chunks::<2>() .as_chunks::<2>()
.0 .0
.iter() .iter()
// Reinterpret the bytes as u16 little-endian. // Reinterpret the bytes as u16 little-endian.
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]])) .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
// Stop at the first null terminator, if any.
// This ignores any trailing data, such as extra null padding.
.take_while(|c| *c != 0)
// Collect the result into a vector. // Collect the result into a vector.
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// Add the null terminator, as the input is not guaranteed to be null-terminated.
data.push(0);
CString16::try_from(data).context("unable to convert utf16 bytes to CString16") CString16::try_from(data).context("unable to convert utf16 bytes to CString16")
} }