From 786171101d5babd9997c8625eeaf37ca22ee80f5 Mon Sep 17 00:00:00 2001 From: Andrew Dunn Date: Mon, 5 Oct 2026 06:54:02 -0400 Subject: [PATCH] install: Reject --stateroot with the composefs backend The composefs backend has a single stateroot: the shared /var lives at state/os/default/var and nothing in its install path reads the stateroot name. So `--stateroot myroot` together with `--composefs-backend` (or with an image that selects that backend itself) exited successfully and installed into `default` anyway. Fail early in InstallComposefsOpts::validate instead, the same way `--bootloader=none` is rejected. This covers to-disk, to-filesystem and to-existing-root, which all go through prepare_install. Passing `--stateroot default` names what the backend uses and stays accepted. Closes: #2542 Assisted-by: AI Signed-off-by: Andrew Dunn --- crates/lib/src/install.rs | 53 +++++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 8f9f80fc4..8b239f85d 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -420,12 +420,22 @@ pub(crate) struct InstallComposefsOpts { impl InstallComposefsOpts { /// Check that the options fit together, once `composefs_backend` says /// whether the composefs backend is used (passed, or selected by the image). - pub(crate) fn validate(&self, bootloader: Option<&Bootloader>) -> Result<()> { + pub(crate) fn validate( + &self, + bootloader: Option<&Bootloader>, + stateroot: Option<&str>, + ) -> Result<()> { if self.composefs_backend { anyhow::ensure!( !matches!(bootloader, Some(Bootloader::None)), "Bootloader set to none is not supported with the composefs backend" ); + let default = ostree_container::deploy::STATEROOT_DEFAULT; + if let Some(stateroot) = stateroot.filter(|&s| s != default) { + anyhow::bail!( + "--stateroot {stateroot} is not supported with the composefs backend, which only uses {default}" + ); + } } else { anyhow::ensure!( !self.allow_missing_verity, @@ -1776,7 +1786,10 @@ async fn prepare_install( )?; tracing::debug!("Composefs default: {composefs_default}"); composefs_options.composefs_backend |= composefs_required || composefs_default; - composefs_options.validate(config_opts.bootloader.as_ref())?; + composefs_options.validate( + config_opts.bootloader.as_ref(), + config_opts.stateroot.as_deref(), + )?; // Read the file eagerly so we error out early, and before the mount changes // below hide a file bind mounted under e.g. /tmp. We may re-exec further down @@ -3134,28 +3147,42 @@ mod tests { #[test] fn test_composefs_opts_validate() { let addon = || Some(vec!["addon".to_string()]); - // (composefs_backend, allow_missing_verity, uki_addon, bootloader, valid) + // (composefs_backend, allow_missing_verity, uki_addon, bootloader, stateroot, valid) let cases = [ - (false, false, None, None, true), - (false, false, None, Some(Bootloader::None), true), - (false, true, None, None, false), - (false, false, addon(), None, false), - (true, false, None, None, true), - (true, true, addon(), Some(Bootloader::Systemd), true), - (true, false, None, Some(Bootloader::None), false), + (false, false, None, None, None, true), + (false, false, None, Some(Bootloader::None), None, true), + (false, true, None, None, None, false), + (false, false, addon(), None, None, false), + (false, false, None, None, Some("myroot"), true), + (true, false, None, None, None, true), + (true, true, addon(), Some(Bootloader::Systemd), None, true), + (true, false, None, Some(Bootloader::None), None, false), + (true, false, None, None, Some("default"), true), + (true, false, None, None, Some("myroot"), false), ]; - for (composefs_backend, allow_missing_verity, uki_addon, bootloader, valid) in cases { + for (composefs_backend, allow_missing_verity, uki_addon, bootloader, stateroot, valid) in + cases + { let opts = InstallComposefsOpts { composefs_backend, allow_missing_verity, uki_addon, }; assert_eq!( - opts.validate(bootloader.as_ref()).is_ok(), + opts.validate(bootloader.as_ref(), stateroot).is_ok(), valid, - "{opts:?} {bootloader:?}" + "{opts:?} {bootloader:?} {stateroot:?}" ); } + + let opts = InstallComposefsOpts { + composefs_backend: true, + ..Default::default() + }; + assert_eq!( + opts.validate(None, Some("myroot")).unwrap_err().to_string(), + "--stateroot myroot is not supported with the composefs backend, which only uses default" + ); } #[test]