feat(boot): read only the boot partitions for BLS in strict mode

In strict mode autoconfigure reads BLS entries from the partition Sprout
was loaded from and the extended boot loader partition of its disk, with
one generator, and no longer makes entries from other disks. Other
filesystems that have BLS are still not scanned for Linux kernels.
This commit is contained in:
alex authored and Alex Zenla committed 2026-10-04 10:55:07 -07:00
1 parent ca01353df4
commit 14463d5776
3 files changed
+128 -37

No files matched your search

+78 -10
View File
@@ -1,8 +1,11 @@
use alloc::boxed::Box;
use anyhow::{Context, Result};
use edera_sprout_config::RootConfiguration;
use log::warn;
use uefi::Handle;
use uefi::fs::FileSystem;
use uefi::proto::device_path::DevicePath;
use uefi::proto::loaded_image::LoadedImage;
use uefi::proto::media::fs::SimpleFileSystem;
/// bls: autodetect and configure BLS-enabled filesystems.
@@ -16,13 +19,47 @@ pub mod linux;
/// windows: autodetect and configure Windows boot configurations.
pub mod windows;
/// Find the filesystems that systemd-boot reads BLS entries from: the partition that Sprout
/// was loaded from, and the extended boot loader partition of the same disk.
fn strict_bls_sources(loaded_image_path: &DevicePath) -> (Option<Handle>, Option<Handle>) {
let own = eficore::handle::open_shared::<LoadedImage>(uefi::boot::image_handle())
.ok()
.and_then(|image| image.device());
let xbootldr = match eficore::xbootldr::find_xbootldr(loaded_image_path) {
Ok(Some(path)) => uefi::boot::locate_device_path::<SimpleFileSystem>(&mut &*path).ok(),
Ok(None) => None,
Err(error) => {
warn!("unable to find the xbootldr partition: {:#}", error);
None
}
};
(own, xbootldr)
}
/// Generate a [RootConfiguration] based on the environment.
/// Intakes a `config` to use as the basis of the autoconfiguration.
pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
/// With `strict`, only the partition Sprout was loaded from and the extended boot loader
/// partition of its disk are read for BLS entries, as one generator, like systemd-boot does.
pub fn autoconfigure(
config: &mut RootConfiguration,
strict: bool,
loaded_image_path: &DevicePath,
) -> Result<()> {
// Find all the filesystems that are on the system.
let filesystem_handles =
uefi::boot::find_handles::<SimpleFileSystem>().context("unable to scan filesystems")?;
// The partitions that BLS entries are read from in strict mode.
let (own_handle, xbootldr_handle) = if strict {
strict_bls_sources(loaded_image_path)
} else {
(None, None)
};
// The device path of the partition of Sprout, which names the strict generator.
let mut own_root: Option<Box<DevicePath>> = None;
// Whether either of the partitions has BLS entries, in strict mode.
let mut strict_found = false;
// For each filesystem that was detected, scan it for supported autoconfig mechanisms.
for handle in filesystem_handles {
// Acquire the device path root for the filesystem.
@@ -51,15 +88,37 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
// A scan that fails is skipped, as a filesystem that can't be read should not
// prevent autoconfiguring the other filesystems.
// Scan the filesystem for BLS supported configurations.
let bls_found = match bls::scan(&mut filesystem, &root, config) {
Ok(found) => found,
Err(error) => {
warn!(
"unable to scan filesystem for bls configurations: {:#}",
error
);
false
// Scan the filesystem for BLS supported configurations. In strict mode, only the
// partitions that systemd-boot reads get entries, and they get a single generator.
// The other filesystems that have BLS are still not scanned for Linux configurations.
let is_source = Some(handle) == own_handle || Some(handle) == xbootldr_handle;
if strict && Some(handle) == own_handle {
own_root = Some(root.to_boxed());
}
let bls_found = if strict {
match bls::detect(&mut filesystem) {
Ok(found) => {
strict_found |= found && is_source;
found
}
Err(error) => {
warn!(
"unable to scan filesystem for bls configurations: {:#}",
error
);
false
}
}
} else {
match bls::scan(&mut filesystem, &root, config) {
Ok(found) => found,
Err(error) => {
warn!(
"unable to scan filesystem for bls configurations: {:#}",
error
);
false
}
}
};
@@ -80,5 +139,14 @@ pub fn autoconfigure(config: &mut RootConfiguration) -> Result<()> {
}
}
// In strict mode, one generator reads both partitions, with the root of Sprout's partition.
if strict
&& strict_found
&& let Some(root) = own_root
&& let Err(error) = bls::add_generator(config, &root, true)
{
warn!("unable to add the bls generator: {:#}", error);
}
Ok(())
}
+44 -26
View File
@@ -18,12 +18,8 @@ use uefi::proto::device_path::text::{AllowShortcuts, DisplayOnly};
/// by the BLS generator to chainload entries.
const BLS_CHAINLOAD_ACTION_PREFIX: &str = "bls-chainload-";
/// Scan the specified `filesystem` for BLS configurations.
pub fn scan(
filesystem: &mut FileSystem,
root: &DevicePath,
config: &mut RootConfiguration,
) -> Result<bool> {
/// Detect whether the `filesystem` has a BLS supported configuration.
pub fn detect(filesystem: &mut FileSystem) -> Result<bool> {
// BLS has a loader.conf file that can specify its own auto-entries mechanism.
let bls_loader_conf_path = Path::new(cstr16!("\\loader\\loader.conf"));
// BLS also has an entries directory that can specify explicit entries.
@@ -31,17 +27,6 @@ pub fn scan(
// BLS Type #2 entries are unified kernel images in the EFI/Linux directory.
let bls_uki_path = Path::new(cstr16!("\\EFI\\Linux"));
// Convert the device path root to a string we can use in the configuration.
let mut root = root
.to_string16(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert device root to string")?
.to_string();
// Add a trailing forward-slash to the root to ensure the device root is completed.
root.push('/');
// Generate a unique hash of the root path.
let root_unique_hash = unique_hash(&root);
// Whether we have a loader.conf file.
let has_loader_conf = filesystem
.try_exists(bls_loader_conf_path)
@@ -86,9 +71,26 @@ pub fn scan(
// Detect if a BLS supported configuration is on this filesystem.
// We check loader.conf, the entries directory and unified kernel images, as only one of
// them is required.
if !(has_loader_conf || has_entries_dir || has_ukis) {
return Ok(false);
}
Ok(has_loader_conf || has_entries_dir || has_ukis)
}
/// Add the BLS generator and its action for the partition with the device path `root`.
/// In `strict` mode, the generator also reads the extended boot loader partition of the disk.
pub fn add_generator(
config: &mut RootConfiguration,
root: &DevicePath,
strict: bool,
) -> Result<()> {
// Convert the device path root to a string we can use in the configuration.
let mut root = root
.to_string16(DisplayOnly(false), AllowShortcuts(false))
.context("unable to convert device root to string")?
.to_string();
// Add a trailing forward-slash to the root to ensure the device root is completed.
root.push('/');
// Generate a unique hash of the root path.
let root_unique_hash = unique_hash(&root);
// Generate a unique name for the BLS chainload action.
let chainload_action_name = format!("{}{}", BLS_CHAINLOAD_ACTION_PREFIX, root_unique_hash,);
@@ -103,8 +105,9 @@ pub fn scan(
path: format!("{}\\loader", root),
uki_path: None,
// Every filesystem is scanned, including the Extended Boot Loader Partition, which
// gets a generator of its own.
xbootldr: false,
// gets a generator of its own, unless strict mode reads it with the generator of the
// partition that Sprout was loaded from.
xbootldr: strict,
pin_names: true,
};
@@ -121,15 +124,18 @@ pub fn scan(
// BLS will provide these values to us.
// Every initrd slot is chained in order. Unused slots stamp to the root of the
// filesystem, which the chainload action skips.
// The files of an entry are on the partition of the entry, which is the root of the
// generator for most entries, and the extended boot loader partition for some in strict mode.
let entry_root = if strict { "$entry-root" } else { &root };
let chainload = ChainloadConfiguration {
path: format!("{}\\$chainload", root),
path: format!("{}\\$chainload", entry_root),
options: vec!["$options".to_string()],
linux_initrd: None,
linux_initrd_chain: (0..BLS_INITRD_SLOTS)
.map(|slot| format!("{}\\$initrd-{}", root, slot))
.map(|slot| format!("{}\\$initrd-{}", entry_root, slot))
.collect(),
// An unset devicetree stamps to the root of the filesystem, which the action skips.
devicetree: Some(format!("{}\\$devicetree", root)),
devicetree: Some(format!("{}\\$devicetree", entry_root)),
};
// Insert the chainload action into the configuration.
@@ -141,6 +147,18 @@ pub fn scan(
},
);
// We had a BLS supported configuration, so return true.
Ok(())
}
/// Scan the specified `filesystem` for BLS configurations.
pub fn scan(
filesystem: &mut FileSystem,
root: &DevicePath,
config: &mut RootConfiguration,
) -> Result<bool> {
if !detect(filesystem)? {
return Ok(false);
}
add_generator(config, root, false)?;
Ok(true)
}
+6 -1
View File
@@ -230,7 +230,12 @@ fn run(reboot_on_error: &mut bool) -> Result<()> {
// If --autoconfigure is specified or the loaded configuration has autoconfigure enabled,
// trigger the autoconfiguration mechanism.
if context.root().options().autoconfigure || config.options.autoconfigure {
autoconfigure::autoconfigure(&mut config).context("unable to autoconfigure")?;
autoconfigure::autoconfigure(
&mut config,
context.root().options().bls_strict_mode,
context.root().loaded_image_path()?,
)
.context("unable to autoconfigure")?;
}
// Unload the context so that it can be modified.