From 3439d1f7f4d31c9e0ea7aa7ce5ed7138a75420cd Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 10:19:00 +0530 Subject: [PATCH 1/6] composefs: Handle fs-verity disabled insall/updates Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/boot.rs | 30 ++++++++++++++----- crates/lib/src/bootc_composefs/finalize.rs | 12 ++++++-- crates/lib/src/bootc_composefs/repo.rs | 12 ++++++-- crates/lib/src/bootc_composefs/selinux.rs | 2 +- crates/lib/src/bootc_composefs/soft_reboot.rs | 6 +++- crates/lib/src/bootc_composefs/state.rs | 6 +++- crates/lib/src/bootc_composefs/status.rs | 1 - crates/lib/src/bootc_composefs/update.rs | 12 ++++++-- crates/lib/src/install.rs | 6 ++-- crates/lib/src/parsers/bls_config.rs | 10 ++++--- 10 files changed, 72 insertions(+), 25 deletions(-) diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index c4787a917..8b35dd5cf 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -94,7 +94,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use crate::parsers::bls_config::{BLSConfig, BLSConfigType}; -use crate::parsers::grub_menuconfig::MenuEntry; use crate::task::Task; use crate::{ bootc_composefs::repo::get_imgref, @@ -119,6 +118,7 @@ use crate::{ }, spec::{Bootloader, Host}, }; +use crate::{parsers::grub_menuconfig::MenuEntry, store::BootedComposefs}; use crate::install::{RootSetup, State}; @@ -155,7 +155,14 @@ pub(crate) enum BootSetupType<'a> { ), ), /// For `bootc upgrade` - Upgrade((&'a Storage, &'a ComposefsFilesystem, &'a Host)), + Upgrade( + ( + &'a Storage, + &'a BootedComposefs, + &'a ComposefsFilesystem, + &'a Host, + ), + ), } #[derive( @@ -532,7 +539,7 @@ pub(crate) fn setup_composefs_bls_boot( ) } - BootSetupType::Upgrade((storage, fs, host)) => { + BootSetupType::Upgrade((storage, booted_cfs, fs, host)) => { let sysroot_parent = get_sysroot_parent_dev(&storage.physical_root)?; let bootloader = host.require_composefs_booted()?.bootloader.clone(); @@ -551,7 +558,12 @@ pub(crate) fn setup_composefs_bls_boot( }; // Copy all cmdline args, replacing only `composefs=` - let param = format!("{COMPOSEFS_CMDLINE}={id_hex}"); + let param = if booted_cfs.cmdline.insecure { + format!("{COMPOSEFS_CMDLINE}=?{id_hex}") + } else { + format!("{COMPOSEFS_CMDLINE}={id_hex}") + }; + let param = Parameter::parse(¶m).context("Failed to create 'composefs=' parameter")?; cmdline.add_or_modify(¶m); @@ -1080,7 +1092,7 @@ pub(crate) fn setup_composefs_uki_boot( ) } - BootSetupType::Upgrade((storage, _, host)) => { + BootSetupType::Upgrade((storage, booted_cfs, _, host)) => { let sysroot = Utf8PathBuf::from("/sysroot"); // Still needed for root_path let sysroot_parent = get_sysroot_parent_dev(&storage.physical_root)?; let bootloader = host.require_composefs_booted()?.bootloader.clone(); @@ -1089,7 +1101,7 @@ pub(crate) fn setup_composefs_uki_boot( sysroot, get_esp_partition(&sysroot_parent)?.0, bootloader, - false, + booted_cfs.cmdline.insecure, None, ) } @@ -1219,8 +1231,11 @@ pub(crate) async fn setup_composefs_boot( root_setup: &RootSetup, state: &State, image_id: &str, + insecure: bool, ) -> Result<()> { - let repo = open_composefs_repo(&root_setup.physical_root)?; + let mut repo = open_composefs_repo(&root_setup.physical_root)?; + repo.set_insecure(insecure); + let mut fs = create_composefs_filesystem(&repo, image_id, None)?; let entries = fs.transform_for_boot(&repo)?; let id = fs.commit_image(&repo, None)?; @@ -1291,6 +1306,7 @@ pub(crate) async fn setup_composefs_boot( &state.source.imageref.name, )) .await?, + insecure, ) .await?; diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index 0f8ffab08..f7451ddcc 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -24,7 +24,11 @@ pub(crate) async fn get_etc_diff(storage: &Storage, booted_cfs: &BootedComposefs // Mount the booted EROFS image to get pristine etc let sysroot_fd = storage.physical_root.reopen_as_ownedfd()?; - let composefs_fd = mount_composefs_image(&sysroot_fd, &booted_composefs.verity, false)?; + let composefs_fd = mount_composefs_image( + &sysroot_fd, + &booted_composefs.verity, + booted_cfs.cmdline.insecure, + )?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; @@ -68,7 +72,11 @@ pub(crate) async fn composefs_backend_finalize( // Mount the booted EROFS image to get pristine etc let sysroot_fd = storage.physical_root.reopen_as_ownedfd()?; - let composefs_fd = mount_composefs_image(&sysroot_fd, &booted_composefs.verity, false)?; + let composefs_fd = mount_composefs_image( + &sysroot_fd, + &booted_composefs.verity, + booted_cfs.cmdline.insecure, + )?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; diff --git a/crates/lib/src/bootc_composefs/repo.rs b/crates/lib/src/bootc_composefs/repo.rs index 0c497fcdc..0532cb3a2 100644 --- a/crates/lib/src/bootc_composefs/repo.rs +++ b/crates/lib/src/bootc_composefs/repo.rs @@ -23,6 +23,7 @@ pub(crate) fn open_composefs_repo(rootfs_dir: &Dir) -> Result Result<(String, impl FsVerityHashValue)> { let rootfs_dir = &root_setup.physical_root; @@ -30,7 +31,8 @@ pub(crate) async fn initialize_composefs_repository( .create_dir_all("composefs") .context("Creating dir composefs")?; - let repo = open_composefs_repo(rootfs_dir)?; + let mut repo = open_composefs_repo(rootfs_dir)?; + repo.set_insecure(insecure); let OstreeExtImgRef { name: image_name, @@ -73,6 +75,7 @@ pub(crate) fn get_imgref(transport: &str, image: &str) -> String { pub(crate) async fn pull_composefs_repo( transport: &String, image: &String, + insecure: bool, ) -> Result<( crate::store::ComposefsRepository, Vec>, @@ -81,7 +84,8 @@ pub(crate) async fn pull_composefs_repo( )> { let rootfs_dir = Dir::open_ambient_dir("/sysroot", ambient_authority())?; - let repo = open_composefs_repo(&rootfs_dir).context("Opening composefs repo")?; + let mut repo = open_composefs_repo(&rootfs_dir).context("Opening composefs repo")?; + repo.set_insecure(insecure); let final_imgref = get_imgref(transport, image); @@ -93,7 +97,9 @@ pub(crate) async fn pull_composefs_repo( tracing::info!("ID: {id}, Verity: {}", verity.to_hex()); - let repo = open_composefs_repo(&rootfs_dir)?; + let mut repo = open_composefs_repo(&rootfs_dir)?; + repo.set_insecure(insecure); + let mut fs: crate::store::ComposefsFilesystem = create_composefs_filesystem(&repo, &id, None) .context("Failed to create composefs filesystem")?; diff --git a/crates/lib/src/bootc_composefs/selinux.rs b/crates/lib/src/bootc_composefs/selinux.rs index 700275264..744c0ba0f 100644 --- a/crates/lib/src/bootc_composefs/selinux.rs +++ b/crates/lib/src/bootc_composefs/selinux.rs @@ -76,7 +76,7 @@ fn get_selinux_policy_for_deployment( let (deployment_root, _mount_guard) = if *booted_cmdline.digest == *depl_id { (Dir::open_ambient_dir("/", ambient_authority())?, None) } else { - let composefs_fd = mount_composefs_image(&sysroot_fd, depl_id, false)?; + let composefs_fd = mount_composefs_image(&sysroot_fd, depl_id, booted_cmdline.insecure)?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; (erofs_tmp_mnt.fd.try_clone()?, Some(erofs_tmp_mnt)) diff --git a/crates/lib/src/bootc_composefs/soft_reboot.rs b/crates/lib/src/bootc_composefs/soft_reboot.rs index 0a71d15b9..0070e41f2 100644 --- a/crates/lib/src/bootc_composefs/soft_reboot.rs +++ b/crates/lib/src/bootc_composefs/soft_reboot.rs @@ -108,7 +108,11 @@ pub(crate) async fn prepare_soft_reboot_composefs( create_dir_all(NEXTROOT).context("Creating nextroot")?; - let cmdline = Cmdline::from(format!("{COMPOSEFS_CMDLINE}={deployment_id}")); + let cmdline = if booted_cfs.cmdline.insecure { + Cmdline::from(format!("{COMPOSEFS_CMDLINE}=?{deployment_id}")) + } else { + Cmdline::from(format!("{COMPOSEFS_CMDLINE}={deployment_id}")) + }; let args = bootc_initramfs_setup::Args { cmd: vec![], diff --git a/crates/lib/src/bootc_composefs/state.rs b/crates/lib/src/bootc_composefs/state.rs index 517281be0..2236bac3c 100644 --- a/crates/lib/src/bootc_composefs/state.rs +++ b/crates/lib/src/bootc_composefs/state.rs @@ -87,6 +87,7 @@ pub(crate) fn initialize_state( erofs_id: &String, state_path: &Utf8PathBuf, initialize_var: bool, + insecure: bool, ) -> Result<()> { let sysroot_fd = open( sysroot_path.as_std_path(), @@ -95,7 +96,8 @@ pub(crate) fn initialize_state( ) .context("Opening sysroot")?; - let composefs_fd = bootc_initramfs_setup::mount_composefs_image(&sysroot_fd, &erofs_id, false)?; + let composefs_fd = + bootc_initramfs_setup::mount_composefs_image(&sysroot_fd, &erofs_id, insecure)?; let tempdir = TempMount::mount_fd(composefs_fd)?; @@ -234,6 +236,7 @@ pub(crate) async fn write_composefs_state( boot_type: BootType, boot_digest: String, container_details: &ImgConfigManifest, + insecure: bool, ) -> Result<()> { let state_path = root_path .join(STATE_DIR_RELATIVE) @@ -256,6 +259,7 @@ pub(crate) async fn write_composefs_state( &deployment_id.to_hex(), &state_path, staged.is_none(), + insecure, )?; let ImageReference { diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index 14fb0bf7b..a70efeb60 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -55,7 +55,6 @@ pub(crate) struct ImgConfigManifest { /// A parsed composefs command line #[derive(Clone)] pub(crate) struct ComposefsCmdline { - #[allow(dead_code)] pub insecure: bool, pub digest: Box, } diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index 978a45360..11b26c8d2 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -249,7 +249,12 @@ pub(crate) async fn do_upgrade( ) -> Result<()> { start_finalize_stated_svc()?; - let (repo, entries, id, fs) = pull_composefs_repo(&imgref.transport, &imgref.image).await?; + let (repo, entries, id, fs) = pull_composefs_repo( + &imgref.transport, + &imgref.image, + booted_cfs.cmdline.insecure, + ) + .await?; let Some(entry) = entries.iter().next() else { anyhow::bail!("No boot entries!"); @@ -265,7 +270,7 @@ pub(crate) async fn do_upgrade( let boot_digest = match boot_type { BootType::Bls => setup_composefs_bls_boot( - BootSetupType::Upgrade((storage, &fs, &host)), + BootSetupType::Upgrade((storage, booted_cfs, &fs, &host)), repo, &id, entry, @@ -273,7 +278,7 @@ pub(crate) async fn do_upgrade( )?, BootType::Uki => setup_composefs_uki_boot( - BootSetupType::Upgrade((storage, &fs, &host)), + BootSetupType::Upgrade((storage, booted_cfs, &fs, &host)), repo, &id, entries, @@ -291,6 +296,7 @@ pub(crate) async fn do_upgrade( boot_type, boot_digest, img_manifest_config, + booted_cfs.cmdline.insecure, ) .await?; diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index a056d03b7..35595e985 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -1887,10 +1887,12 @@ async fn install_to_filesystem_impl( if state.composefs_options.composefs_backend { // Load a fd for the mounted target physical root - let (id, verity) = initialize_composefs_repository(state, rootfs).await?; + let (id, verity) = + initialize_composefs_repository(state, rootfs, state.composefs_options.insecure) + .await?; tracing::info!("id: {id}, verity: {}", verity.to_hex()); - setup_composefs_boot(rootfs, state, &id).await?; + setup_composefs_boot(rootfs, state, &id, state.composefs_options.insecure).await?; } else { ostree_install(state, rootfs, cleanup).await?; } diff --git a/crates/lib/src/parsers/bls_config.rs b/crates/lib/src/parsers/bls_config.rs index 66716b88e..a783053bc 100644 --- a/crates/lib/src/parsers/bls_config.rs +++ b/crates/lib/src/parsers/bls_config.rs @@ -13,6 +13,7 @@ use std::collections::HashMap; use std::fmt::Display; use uapi_version::Version; +use crate::bootc_composefs::status::ComposefsCmdline; use crate::composefs_consts::COMPOSEFS_CMDLINE; #[derive(Debug, PartialEq, Eq, Default)] @@ -189,15 +190,16 @@ impl BLSConfig { let kv = cmdline .find(COMPOSEFS_CMDLINE) - .ok_or(anyhow::anyhow!("No composefs= param"))?; + .ok_or_else(|| anyhow::anyhow!("No composefs= param"))?; let value = kv .value() - .ok_or(anyhow::anyhow!("Empty composefs= param"))?; + .ok_or_else(|| anyhow::anyhow!("Empty composefs= param"))?; - let value = value.to_owned(); + let cfs_cmdline = ComposefsCmdline::new(value); - Ok(value) + // TODO(Johan-Liebert1): We lose the info here that this is insecure + Ok(cfs_cmdline.digest.to_string().clone()) } BLSConfigType::Unknown => anyhow::bail!("Unknown config type"), From 864413bd192967c4063f6a912ef01a5dc15b8a9e Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 10:35:29 +0530 Subject: [PATCH 2/6] composefs: Fix unqueuing rollback We were simply checking to booted system's verity and simply setting the corresponding boot entry as the secondary boot entry, even if rollback was already queued. Update the code to actually consider the bootloader entries as the source of truth, similar to what ostree does Signed-off-by: Pragyan Poudyal --- crates/lib/src/bootc_composefs/rollback.rs | 35 ++++++++++------------ 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/crates/lib/src/bootc_composefs/rollback.rs b/crates/lib/src/bootc_composefs/rollback.rs index fd30e99a2..56e6de3d2 100644 --- a/crates/lib/src/bootc_composefs/rollback.rs +++ b/crates/lib/src/bootc_composefs/rollback.rs @@ -114,36 +114,34 @@ fn rollback_grub_uki_entries(boot_dir: &Dir) -> Result<()> { /// - Grub Type1 boot entries /// - Systemd Typ1 boot entries /// - Systemd UKI (Type2) boot entries [since we use BLS entries for systemd boot] +/// +/// Cases +/// 1. We're actually booted into the deployment that has it's sort_key as 0 +/// a. Just swap the primary and secondary bootloader entries +/// b. If they're already swapped (rollback was queued), re-swap them (unqueue rollback) +/// +/// 2. We're booted into the depl with sort_key 1 (choose the rollback deployment on boot screen) +/// a. Here we assume that rollback is queued as there's no way to differentiate between this +/// case and Case 1-b. This is what ostree does as well #[context("Rolling back {bootloader} entries")] fn rollback_composefs_entries(boot_dir: &Dir, bootloader: Bootloader) -> Result<()> { - use crate::bootc_composefs::state::get_booted_bls; - // Get all boot entries sorted in descending order by sort-key let mut all_configs = get_sorted_type1_boot_entries(&boot_dir, false)?; // TODO(Johan-Liebert): Currently assuming there are only two deployments assert!(all_configs.len() == 2); - // Identify which entry is the currently booted one - let booted_bls = get_booted_bls(&boot_dir)?; - let booted_verity = booted_bls.get_verity()?; - // For rollback: previous gets primary sort-key, booted gets secondary sort-key // Use "bootc" as default os_id for rollback scenarios // TODO: Extract actual os_id from deployment let os_id = "bootc"; - for cfg in &mut all_configs { - let cfg_verity = cfg.get_verity()?; - - if cfg_verity == booted_verity { - // This is the currently booted deployment - it should become secondary - cfg.sort_key = Some(secondary_sort_key(os_id)); - } else { - // This is the previous deployment - it should become primary (rollback target) - cfg.sort_key = Some(primary_sort_key(os_id)); - } - } + // This is the currently booted deployment - it should become secondary + // OR if rollback was queued, it would become primary + all_configs[0].sort_key = Some(primary_sort_key(os_id)); + // This is the previous deployment - it should become primary (rollback target) + // OR if rollback was queued, it would become secondary + all_configs[1].sort_key = Some(secondary_sort_key(os_id)); // Write these boot_dir @@ -156,9 +154,8 @@ fn rollback_composefs_entries(boot_dir: &Dir, bootloader: Bootloader) -> Result< // Write the BLS configs in there for cfg in all_configs { - let cfg_verity = cfg.get_verity()?; // After rollback: previous deployment becomes primary, booted becomes secondary - let priority = if cfg_verity == booted_verity { + let priority = if cfg.sort_key == Some(secondary_sort_key(os_id)) { FILENAME_PRIORITY_SECONDARY } else { FILENAME_PRIORITY_PRIMARY From 1ae4b65692a28e1e4c8dda25be68f50f506195da Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 12:19:32 +0530 Subject: [PATCH 3/6] tmt: Add test for rollback Signed-off-by: Pragyan Poudyal --- tmt/plans/integration.fmf | 7 ++ tmt/tests/booted/test-rollback.nu | 117 ++++++++++++++++++++++++++++++ tmt/tests/tests.fmf | 5 ++ 3 files changed, 129 insertions(+) create mode 100644 tmt/tests/booted/test-rollback.nu diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf index 1c8af4881..3aa0f9907 100644 --- a/tmt/plans/integration.fmf +++ b/tmt/plans/integration.fmf @@ -181,4 +181,11 @@ execute: how: fmf test: - /tmt/tests/tests/test-34-user-agent + +/plan-36-rollback: + summary: Test bootc rollback functionality through image switch and rollback cycle + discover: + how: fmf + test: + - /tmt/tests/tests/test-36-rollback # END GENERATED PLANS diff --git a/tmt/tests/booted/test-rollback.nu b/tmt/tests/booted/test-rollback.nu new file mode 100644 index 000000000..0f2e2ee89 --- /dev/null +++ b/tmt/tests/booted/test-rollback.nu @@ -0,0 +1,117 @@ +# number: 36 +# tmt: +# summary: Test bootc rollback functionality +# duration: 30m +# +# This test verifies bootc rollback functionality: +# 1. Captures the initial deployment state +# 2. Switches to a different image +# 3. Verifies the switch was successful +# 4. Performs bootc rollback +# 5. Reboots and verifies we're back to the original deployment + +use std assert +use tap.nu +use bootc_testlib.nu + +bootc status +journalctl --list-boots + +let st = bootc status --json | from json +let booted = $st.status.booted.image + +def imgsrc [] { + $env.BOOTC_upgrade_image? | default "localhost/bootc-derived-local" +} + +# Run on the first boot - capture initial state and switch to new image +def initial_switch [] { + tap begin "bootc rollback test" + + print "=== Initial boot - capturing state and switching image ===" + + # Store initial deployment information for later verification + let initial_st = bootc status --json | from json + let initial_image = $initial_st.status.booted.image + + $initial_image | to json | save /var/bootc-initial-state.json + + let imgsrc = imgsrc + + if ($imgsrc | str ends-with "-local") { + bootc image copy-to-storage + + print "Building derived container" + "FROM localhost/bootc +RUN echo 'This is the rollback target image' > /usr/share/bootc-rollback-marker +" | save Dockerfile + + podman build -t $imgsrc . + print $"Built derived image: ($imgsrc)" + } + + print $"Switching to ($imgsrc)" + bootc switch --transport containers-storage $imgsrc + + print "Switch completed, rebooting to new image..." + tmt-reboot +} + +# Check that we successfully switched to the new image and then rollback +def second_boot_rollback [] { + print "=== Second boot - verifying switch and performing rollback ===" + + # Verify we're running the new image + assert equal $booted.image.image $"(imgsrc)" + print "Successfully switched to new image" + + assert ("/usr/share/bootc-rollback-marker" | path exists) + print "New image artifacts verified" + + print "Performing bootc rollback..." + bootc rollback + + print "Rollback initiated, rebooting to previous deployment..." + tmt-reboot +} + +def back_to_first_depl [boot_count] { + print $"=== ($boot_count) boot - verifying rollback success ===" + + # Load the original state we saved and verify we're back to the original image + let original_state = cat /var/bootc-initial-state.json | from json + + assert equal $booted.image $original_state.image + print $"Successfully rolled back to original image: ($booted.image.image)" + + if ("/usr/share/bootc-rollback-marker" | path exists) { + error make { msg: "Rollback target marker still present - rollback may have failed" } + } +} + +# Verify that rollback was successful and we're back to original deployment +def third_boot_verify [] { + back_to_first_depl Third + + # Finally test a double rollback, to make sure the rollback state is queued then unqueued + bootc rollback + bootc rollback + + tmt-reboot +} + +def fourth_boot_verify [] { + back_to_first_depl Fourth + tap ok +} + +def main [] { + # See https://tmt.readthedocs.io/en/stable/stories/features.html#reboot-during-test + match $env.TMT_REBOOT_COUNT? { + null | "0" => initial_switch, + "1" => second_boot_rollback, + "2" => third_boot_verify, + "3" => fourth_boot_verify, + $o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } }, + } +} diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf index 4d808880e..a1f5980e2 100644 --- a/tmt/tests/tests.fmf +++ b/tmt/tests/tests.fmf @@ -101,3 +101,8 @@ summary: Verify bootc sends correct User-Agent header to registries duration: 10m test: python3 booted/test-user-agent.py + +/test-36-rollback: + summary: Test bootc rollback functionality through image switch and rollback cycle + duration: 30m + test: nu booted/test-rollback.nu From f76cf4aaf39a46c3ebc62b51d6b32f6c810676c3 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 13:28:34 +0530 Subject: [PATCH 4/6] composefs/tests: Add tests for filesystems Add filesystems ext4 and xfs in github CI matrix so that we test systems with and without fs-verity enabled Signed-off-by: Pragyan Poudyal --- .github/workflows/ci.yml | 12 ++++++++---- Justfile | 23 ++++++++++++++--------- crates/xtask/src/tmt.rs | 11 ++++++++--- crates/xtask/src/xtask.rs | 3 +++ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a87d3ea25..6797e5004 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,6 +162,7 @@ jobs: # No fedora-44 due to https://bugzilla.redhat.com/show_bug.cgi?id=2429501 test_os: [fedora-43, centos-9, centos-10] variant: [ostree, composefs-sealeduki-sdboot, composefs-sdboot, composefs-grub] + filesystem: ["ext4", "xfs"] exclude: # centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812) - test_os: centos-9 @@ -172,6 +173,10 @@ jobs: variant: composefs-sdboot - test_os: centos-9 variant: composefs-grub + # We only test filesystems for composefs to test if composefs backend will work on fs + # without fsverity + - variant: ostree + filesystem: ext4 runs-on: ubuntu-24.04 @@ -190,6 +195,7 @@ jobs: echo "BOOTC_base=${BASE}" >> $GITHUB_ENV echo "RUST_BACKTRACE=full" >> $GITHUB_ENV echo "RUST_LOG=trace" >> $GITHUB_ENV + echo "BOOTC_filesystem=${{ matrix.filesystem }}" >> $GITHUB_ENV case "${{ matrix.variant }}" in composefs-grub) @@ -213,8 +219,6 @@ jobs: ;; esac - - if [ "${{ matrix.variant }}" = "composefs-sealeduki-sdboot" ]; then BUILDROOTBASE=$(just pullspec-for-os buildroot-base ${{ matrix.test_os }}) echo "BOOTC_buildroot_base=${BUILDROOTBASE}" >> $GITHUB_ENV @@ -244,7 +248,7 @@ jobs: - name: Run TMT integration tests run: | if [[ "${{ matrix.variant }}" = composefs* ]]; then - just "test-${{ matrix.variant }}" + just "test-${{ matrix.variant }}" "${{ matrix.filesystem }}" else just test-tmt integration fi @@ -255,7 +259,7 @@ jobs: if: always() uses: actions/upload-artifact@v6 with: - name: tmt-log-PR-${{ github.event.number }}-${{ matrix.test_os }}-${{ matrix.variant }}-${{ env.ARCH }} + name: tmt-log-PR-${{ github.event.number }}-${{ matrix.test_os }}-${{ matrix.variant }}-${{ matrix.filesystem }}-${{ env.ARCH }} path: /var/tmp/tmt # Test bootc install on Fedora CoreOS (separate job to avoid disk space issues diff --git a/Justfile b/Justfile index 176ff81e3..7b18ad9bf 100644 --- a/Justfile +++ b/Justfile @@ -21,6 +21,8 @@ upgrade_img := base_img + "-upgrade" # Build variant: ostree (default) or composefs-sealeduki-sdboot (sealed UKI) variant := env("BOOTC_variant", "ostree") bootloader := env("BOOTC_bootloader", "grub") +# Only used for composefs tests +filesystem := env("BOOTC_filesystem", "ext4") # Base container image to build from base := env("BOOTC_base", "quay.io/centos-bootc/centos-bootc:stream10") # Buildroot base image @@ -106,23 +108,26 @@ test-container: build build-units # Build and test sealed composefs images [group('core')] -test-composefs-sealeduki-sdboot: - just variant=composefs-sealeduki-sdboot test-tmt readonly local-upgrade-reboot +test-composefs-sealeduki-sdboot filesystem: + just variant=composefs-sealeduki-sdboot filesystem={{filesystem}} test-tmt readonly local-upgrade-reboot [group('core')] -test-composefs bootloader: - just variant=composefs bootloader={{bootloader}} \ - test-tmt --composefs-backend --bootloader {{bootloader}} integration +test-composefs bootloader filesystem: + just variant=composefs bootloader={{bootloader}} filesystem={{filesystem}} \ + test-tmt --composefs-backend \ + --bootloader {{bootloader}} \ + --filesystem {{filesystem}} \ + integration # Build and test composefs images booted using Type1 boot entries and systemd-boot as the bootloader [group('core')] -test-composefs-sdboot: - just test-composefs systemd +test-composefs-sdboot filesystem: + just test-composefs systemd {{filesystem}} # Build and test composefs images booted using Type1 boot entries and grub as the bootloader [group('core')] -test-composefs-grub: - just test-composefs grub +test-composefs-grub filesystem: + just test-composefs grub {{filesystem}} # Run cargo fmt and clippy checks in container [group('core')] diff --git a/crates/xtask/src/tmt.rs b/crates/xtask/src/tmt.rs index e244229c1..4f7e0b9da 100644 --- a/crates/xtask/src/tmt.rs +++ b/crates/xtask/src/tmt.rs @@ -485,10 +485,15 @@ pub(crate) fn run_tmt(sh: &Shell, args: &RunTmtArgs) -> Result<()> { } if args.composefs_backend { - // TODO(Johan-Liebert1): Filesystem should be a parameter and we should test - // insecure with xfs - opts.push("--filesystem=ext4".into()); + let filesystem = args.filesystem.as_deref().unwrap_or("ext4"); + opts.push(format!("--filesystem={}", filesystem)); opts.push("--composefs-backend".into()); + + if filesystem == "xfs" { + // As xfs doesn't support fsverity + opts.push("--allow-missing-verity".into()); + } + opts.extend(COMPOSEFS_KERNEL_ARGS.map(|x| x.into())); } diff --git a/crates/xtask/src/xtask.rs b/crates/xtask/src/xtask.rs index cb6afe29f..056bd780e 100644 --- a/crates/xtask/src/xtask.rs +++ b/crates/xtask/src/xtask.rs @@ -127,6 +127,9 @@ pub(crate) struct RunTmtArgs { #[arg(long, requires = "composefs_backend")] pub(crate) bootloader: Option, + + #[arg(long, requires = "composefs_backend")] + pub(crate) filesystem: Option, } /// Arguments for tmt-provision command From 6378a61057d9fbb19d435c06d09967885f8b0a50 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 13:40:37 +0530 Subject: [PATCH 5/6] cli: Change `insecure` param to `allow_missing_fsverity` `allow_missing_fsverity` conveys the intent in a much better way than just `insecure` Signed-off-by: Pragyan Poudyal --- crates/initramfs/src/lib.rs | 14 +++++--- crates/lib/src/bootc_composefs/boot.rs | 33 ++++++++++--------- crates/lib/src/bootc_composefs/finalize.rs | 4 +-- crates/lib/src/bootc_composefs/repo.rs | 10 +++--- crates/lib/src/bootc_composefs/selinux.rs | 3 +- crates/lib/src/bootc_composefs/soft_reboot.rs | 2 +- crates/lib/src/bootc_composefs/state.rs | 13 +++++--- crates/lib/src/bootc_composefs/status.rs | 18 ++++++---- crates/lib/src/bootc_composefs/update.rs | 4 +-- crates/lib/src/install.rs | 19 ++++++++--- crates/lib/src/store/mod.rs | 2 +- 11 files changed, 73 insertions(+), 49 deletions(-) diff --git a/crates/initramfs/src/lib.rs b/crates/initramfs/src/lib.rs index 1a893b397..5d8decd40 100644 --- a/crates/initramfs/src/lib.rs +++ b/crates/initramfs/src/lib.rs @@ -258,13 +258,17 @@ fn open_root_fs(path: &Path) -> Result { /// Prepares a floating mount for composefs and returns the fd /// /// # Arguments -/// * sysroot - fd for /sysroot -/// * name - Name of the EROFS image to be mounted -/// * insecure - Whether fsverity is optional or not +/// * sysroot - fd for /sysroot +/// * name - Name of the EROFS image to be mounted +/// * allow_missing_fsverity - Whether to allow mount without fsverity support #[context("Mounting composefs image")] -pub fn mount_composefs_image(sysroot: &OwnedFd, name: &str, insecure: bool) -> Result { +pub fn mount_composefs_image( + sysroot: &OwnedFd, + name: &str, + allow_missing_fsverity: bool, +) -> Result { let mut repo = Repository::::open_path(sysroot, "composefs")?; - repo.set_insecure(insecure); + repo.set_insecure(allow_missing_fsverity); let rootfs = repo .mount(name) .context("Failed to mount composefs image")?; diff --git a/crates/lib/src/bootc_composefs/boot.rs b/crates/lib/src/bootc_composefs/boot.rs index 8b35dd5cf..72c0c77d7 100644 --- a/crates/lib/src/bootc_composefs/boot.rs +++ b/crates/lib/src/bootc_composefs/boot.rs @@ -519,7 +519,7 @@ pub(crate) fn setup_composefs_bls_boot( cmdline_options.extend(&root_setup.kargs); - let composefs_cmdline = if state.composefs_options.insecure { + let composefs_cmdline = if state.composefs_options.allow_missing_verity { format!("{COMPOSEFS_CMDLINE}=?{id_hex}") } else { format!("{COMPOSEFS_CMDLINE}={id_hex}") @@ -558,7 +558,7 @@ pub(crate) fn setup_composefs_bls_boot( }; // Copy all cmdline args, replacing only `composefs=` - let param = if booted_cfs.cmdline.insecure { + let param = if booted_cfs.cmdline.allow_missing_fsverity { format!("{COMPOSEFS_CMDLINE}=?{id_hex}") } else { format!("{COMPOSEFS_CMDLINE}={id_hex}") @@ -809,7 +809,7 @@ fn write_pe_to_esp( file_path: &Utf8Path, pe_type: PEType, uki_id: &Sha512HashValue, - is_insecure_from_opts: bool, + missing_fsverity_allowed: bool, mounted_efi: impl AsRef, bootloader: &Bootloader, ) -> Result> { @@ -822,17 +822,19 @@ fn write_pe_to_esp( if matches!(pe_type, PEType::Uki) { let cmdline = uki::get_cmdline(&efi_bin).context("Getting UKI cmdline")?; - let (composefs_cmdline, insecure) = + let (composefs_cmdline, missing_verity_allowed_cmdline) = get_cmdline_composefs::(cmdline).context("Parsing composefs=")?; // If the UKI cmdline does not match what the user has passed as cmdline option // NOTE: This will only be checked for new installs and now upgrades/switches - match is_insecure_from_opts { - true if !insecure => { - tracing::warn!("--insecure passed as option but UKI cmdline does not support it"); + match missing_fsverity_allowed { + true if !missing_verity_allowed_cmdline => { + tracing::warn!( + "--allow-missing-fsverity passed as option but UKI cmdline does not support it" + ); } - false if insecure => { + false if missing_verity_allowed_cmdline => { tracing::warn!("UKI cmdline has composefs set as insecure"); } @@ -1077,7 +1079,8 @@ pub(crate) fn setup_composefs_uki_boot( id: &Sha512HashValue, entries: Vec>, ) -> Result { - let (root_path, esp_device, bootloader, is_insecure_from_opts, uki_addons) = match setup_type { + let (root_path, esp_device, bootloader, missing_fsverity_allowed, uki_addons) = match setup_type + { BootSetupType::Setup((root_setup, state, postfetch, ..)) => { state.require_no_kargs_for_uki()?; @@ -1087,7 +1090,7 @@ pub(crate) fn setup_composefs_uki_boot( root_setup.physical_root_path.clone(), esp_part.node.clone(), postfetch.detected_bootloader.clone(), - state.composefs_options.insecure, + state.composefs_options.allow_missing_verity, state.composefs_options.uki_addon.as_ref(), ) } @@ -1101,7 +1104,7 @@ pub(crate) fn setup_composefs_uki_boot( sysroot, get_esp_partition(&sysroot_parent)?.0, bootloader, - booted_cfs.cmdline.insecure, + booted_cfs.cmdline.allow_missing_fsverity, None, ) } @@ -1152,7 +1155,7 @@ pub(crate) fn setup_composefs_uki_boot( utf8_file_path, entry.pe_type, &id, - is_insecure_from_opts, + missing_fsverity_allowed, esp_mount.dir.path(), &bootloader, )?; @@ -1231,10 +1234,10 @@ pub(crate) async fn setup_composefs_boot( root_setup: &RootSetup, state: &State, image_id: &str, - insecure: bool, + allow_missing_fsverity: bool, ) -> Result<()> { let mut repo = open_composefs_repo(&root_setup.physical_root)?; - repo.set_insecure(insecure); + repo.set_insecure(allow_missing_fsverity); let mut fs = create_composefs_filesystem(&repo, image_id, None)?; let entries = fs.transform_for_boot(&repo)?; @@ -1306,7 +1309,7 @@ pub(crate) async fn setup_composefs_boot( &state.source.imageref.name, )) .await?, - insecure, + allow_missing_fsverity, ) .await?; diff --git a/crates/lib/src/bootc_composefs/finalize.rs b/crates/lib/src/bootc_composefs/finalize.rs index f7451ddcc..af32b6123 100644 --- a/crates/lib/src/bootc_composefs/finalize.rs +++ b/crates/lib/src/bootc_composefs/finalize.rs @@ -27,7 +27,7 @@ pub(crate) async fn get_etc_diff(storage: &Storage, booted_cfs: &BootedComposefs let composefs_fd = mount_composefs_image( &sysroot_fd, &booted_composefs.verity, - booted_cfs.cmdline.insecure, + booted_cfs.cmdline.allow_missing_fsverity, )?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; @@ -75,7 +75,7 @@ pub(crate) async fn composefs_backend_finalize( let composefs_fd = mount_composefs_image( &sysroot_fd, &booted_composefs.verity, - booted_cfs.cmdline.insecure, + booted_cfs.cmdline.allow_missing_fsverity, )?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; diff --git a/crates/lib/src/bootc_composefs/repo.rs b/crates/lib/src/bootc_composefs/repo.rs index 0532cb3a2..7e5318380 100644 --- a/crates/lib/src/bootc_composefs/repo.rs +++ b/crates/lib/src/bootc_composefs/repo.rs @@ -23,7 +23,7 @@ pub(crate) fn open_composefs_repo(rootfs_dir: &Dir) -> Result Result<(String, impl FsVerityHashValue)> { let rootfs_dir = &root_setup.physical_root; @@ -32,7 +32,7 @@ pub(crate) async fn initialize_composefs_repository( .context("Creating dir composefs")?; let mut repo = open_composefs_repo(rootfs_dir)?; - repo.set_insecure(insecure); + repo.set_insecure(allow_missing_fsverity); let OstreeExtImgRef { name: image_name, @@ -75,7 +75,7 @@ pub(crate) fn get_imgref(transport: &str, image: &str) -> String { pub(crate) async fn pull_composefs_repo( transport: &String, image: &String, - insecure: bool, + allow_missing_fsverity: bool, ) -> Result<( crate::store::ComposefsRepository, Vec>, @@ -85,7 +85,7 @@ pub(crate) async fn pull_composefs_repo( let rootfs_dir = Dir::open_ambient_dir("/sysroot", ambient_authority())?; let mut repo = open_composefs_repo(&rootfs_dir).context("Opening composefs repo")?; - repo.set_insecure(insecure); + repo.set_insecure(allow_missing_fsverity); let final_imgref = get_imgref(transport, image); @@ -98,7 +98,7 @@ pub(crate) async fn pull_composefs_repo( tracing::info!("ID: {id}, Verity: {}", verity.to_hex()); let mut repo = open_composefs_repo(&rootfs_dir)?; - repo.set_insecure(insecure); + repo.set_insecure(allow_missing_fsverity); let mut fs: crate::store::ComposefsFilesystem = create_composefs_filesystem(&repo, &id, None) diff --git a/crates/lib/src/bootc_composefs/selinux.rs b/crates/lib/src/bootc_composefs/selinux.rs index 744c0ba0f..733f0897a 100644 --- a/crates/lib/src/bootc_composefs/selinux.rs +++ b/crates/lib/src/bootc_composefs/selinux.rs @@ -76,7 +76,8 @@ fn get_selinux_policy_for_deployment( let (deployment_root, _mount_guard) = if *booted_cmdline.digest == *depl_id { (Dir::open_ambient_dir("/", ambient_authority())?, None) } else { - let composefs_fd = mount_composefs_image(&sysroot_fd, depl_id, booted_cmdline.insecure)?; + let composefs_fd = + mount_composefs_image(&sysroot_fd, depl_id, booted_cmdline.allow_missing_fsverity)?; let erofs_tmp_mnt = TempMount::mount_fd(&composefs_fd)?; (erofs_tmp_mnt.fd.try_clone()?, Some(erofs_tmp_mnt)) diff --git a/crates/lib/src/bootc_composefs/soft_reboot.rs b/crates/lib/src/bootc_composefs/soft_reboot.rs index 0070e41f2..b9e8a6e53 100644 --- a/crates/lib/src/bootc_composefs/soft_reboot.rs +++ b/crates/lib/src/bootc_composefs/soft_reboot.rs @@ -108,7 +108,7 @@ pub(crate) async fn prepare_soft_reboot_composefs( create_dir_all(NEXTROOT).context("Creating nextroot")?; - let cmdline = if booted_cfs.cmdline.insecure { + let cmdline = if booted_cfs.cmdline.allow_missing_fsverity { Cmdline::from(format!("{COMPOSEFS_CMDLINE}=?{deployment_id}")) } else { Cmdline::from(format!("{COMPOSEFS_CMDLINE}={deployment_id}")) diff --git a/crates/lib/src/bootc_composefs/state.rs b/crates/lib/src/bootc_composefs/state.rs index 2236bac3c..b4350c7f0 100644 --- a/crates/lib/src/bootc_composefs/state.rs +++ b/crates/lib/src/bootc_composefs/state.rs @@ -87,7 +87,7 @@ pub(crate) fn initialize_state( erofs_id: &String, state_path: &Utf8PathBuf, initialize_var: bool, - insecure: bool, + allow_missing_fsverity: bool, ) -> Result<()> { let sysroot_fd = open( sysroot_path.as_std_path(), @@ -96,8 +96,11 @@ pub(crate) fn initialize_state( ) .context("Opening sysroot")?; - let composefs_fd = - bootc_initramfs_setup::mount_composefs_image(&sysroot_fd, &erofs_id, insecure)?; + let composefs_fd = bootc_initramfs_setup::mount_composefs_image( + &sysroot_fd, + &erofs_id, + allow_missing_fsverity, + )?; let tempdir = TempMount::mount_fd(composefs_fd)?; @@ -236,7 +239,7 @@ pub(crate) async fn write_composefs_state( boot_type: BootType, boot_digest: String, container_details: &ImgConfigManifest, - insecure: bool, + allow_missing_fsverity: bool, ) -> Result<()> { let state_path = root_path .join(STATE_DIR_RELATIVE) @@ -259,7 +262,7 @@ pub(crate) async fn write_composefs_state( &deployment_id.to_hex(), &state_path, staged.is_none(), - insecure, + allow_missing_fsverity, )?; let ImageReference { diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index a70efeb60..282845a4d 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -55,7 +55,7 @@ pub(crate) struct ImgConfigManifest { /// A parsed composefs command line #[derive(Clone)] pub(crate) struct ComposefsCmdline { - pub insecure: bool, + pub allow_missing_fsverity: bool, pub digest: Box, } @@ -68,12 +68,12 @@ struct DeploymentBootInfo<'a> { impl ComposefsCmdline { pub(crate) fn new(s: &str) -> Self { - let (insecure, digest_str) = s + let (allow_missing_fsverity, digest_str) = s .strip_prefix('?') .map(|v| (true, v)) .unwrap_or_else(|| (false, s)); ComposefsCmdline { - insecure, + allow_missing_fsverity, digest: digest_str.into(), } } @@ -81,8 +81,12 @@ impl ComposefsCmdline { impl std::fmt::Display for ComposefsCmdline { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let insecure = if self.insecure { "?" } else { "" }; - write!(f, "{}={}{}", COMPOSEFS_CMDLINE, insecure, self.digest) + let allow_missing_fsverity = if self.allow_missing_fsverity { "?" } else { "" }; + write!( + f, + "{}={}{}", + COMPOSEFS_CMDLINE, allow_missing_fsverity, self.digest + ) } } @@ -806,10 +810,10 @@ mod tests { fn test_composefs_parsing() { const DIGEST: &str = "8b7df143d91c716ecfa5fc1730022f6b421b05cedee8fd52b1fc65a96030ad52"; let v = ComposefsCmdline::new(DIGEST); - assert!(!v.insecure); + assert!(!v.allow_missing_fsverity); assert_eq!(v.digest.as_ref(), DIGEST); let v = ComposefsCmdline::new(&format!("?{}", DIGEST)); - assert!(v.insecure); + assert!(v.allow_missing_fsverity); assert_eq!(v.digest.as_ref(), DIGEST); } diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index 11b26c8d2..079fad520 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -252,7 +252,7 @@ pub(crate) async fn do_upgrade( let (repo, entries, id, fs) = pull_composefs_repo( &imgref.transport, &imgref.image, - booted_cfs.cmdline.insecure, + booted_cfs.cmdline.allow_missing_fsverity, ) .await?; @@ -296,7 +296,7 @@ pub(crate) async fn do_upgrade( boot_type, boot_digest, img_manifest_config, - booted_cfs.cmdline.insecure, + booted_cfs.cmdline.allow_missing_fsverity, ) .await?; diff --git a/crates/lib/src/install.rs b/crates/lib/src/install.rs index 35595e985..d0c0ab201 100644 --- a/crates/lib/src/install.rs +++ b/crates/lib/src/install.rs @@ -385,7 +385,7 @@ pub(crate) struct InstallComposefsOpts { /// Make fs-verity validation optional in case the filesystem doesn't support it #[clap(long, default_value_t, requires = "composefs_backend")] #[serde(default)] - pub(crate) insecure: bool, + pub(crate) allow_missing_verity: bool, /// The bootloader to use. #[clap(long, requires = "composefs_backend")] @@ -1887,12 +1887,21 @@ async fn install_to_filesystem_impl( if state.composefs_options.composefs_backend { // Load a fd for the mounted target physical root - let (id, verity) = - initialize_composefs_repository(state, rootfs, state.composefs_options.insecure) - .await?; + let (id, verity) = initialize_composefs_repository( + state, + rootfs, + state.composefs_options.allow_missing_verity, + ) + .await?; tracing::info!("id: {id}, verity: {}", verity.to_hex()); - setup_composefs_boot(rootfs, state, &id, state.composefs_options.insecure).await?; + setup_composefs_boot( + rootfs, + state, + &id, + state.composefs_options.allow_missing_verity, + ) + .await?; } else { ostree_install(state, rootfs, cleanup).await?; } diff --git a/crates/lib/src/store/mod.rs b/crates/lib/src/store/mod.rs index 260fe96b5..b5d2470d1 100644 --- a/crates/lib/src/store/mod.rs +++ b/crates/lib/src/store/mod.rs @@ -167,7 +167,7 @@ impl BootedStorage { Environment::ComposefsBooted(cmdline) => { let (physical_root, run) = get_physical_root_and_run()?; let mut composefs = ComposefsRepository::open_path(&physical_root, COMPOSEFS)?; - if cmdline.insecure { + if cmdline.allow_missing_fsverity { composefs.set_insecure(true); } let composefs = Arc::new(composefs); From 87cb40bb336b55e7f4bc9bf5b1b5f860cd2e3b18 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 12 Feb 2026 16:00:54 +0530 Subject: [PATCH 6/6] ukify: Accept `allow-missing-verity` param Signed-off-by: Pragyan Poudyal --- Dockerfile | 10 +++++++++- Justfile | 6 +++++- contrib/packaging/seal-uki | 10 +++++++++- crates/lib/src/cli.rs | 7 ++++++- crates/lib/src/ukify.rs | 11 ++++++++--- 5 files changed, 37 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5422d8e06..019822206 100644 --- a/Dockerfile +++ b/Dockerfile @@ -176,6 +176,7 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp # We need our newly-built bootc for the compute-composefs-digest command FROM tools as sealed-uki ARG variant +ARG filesystem # Install our bootc package (only needed for the compute-composefs-digest command) RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packages,src=/,target=/run/packages \ @@ -186,8 +187,15 @@ RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=bind,from=packaging,src=/,target=/run/packaging \ --mount=type=bind,from=base-penultimate,src=/,target=/run/target <, + /// Make fs-verity validation optional in case the filesystem doesn't support it + #[clap(long)] + allow_missing_verity: bool, + /// Additional arguments to pass to ukify (after `--`). #[clap(last = true)] args: Vec, @@ -1624,8 +1628,9 @@ async fn run_from_opt(opt: Opt) -> Result<()> { ContainerOpts::Ukify { rootfs, kargs, + allow_missing_verity, args, - } => crate::ukify::build_ukify(&rootfs, &kargs, &args), + } => crate::ukify::build_ukify(&rootfs, &kargs, &args, allow_missing_verity), }, Opt::Completion { shell } => { use clap_complete::aot::generate; diff --git a/crates/lib/src/ukify.rs b/crates/lib/src/ukify.rs index 05e5d86ed..7419de2fb 100644 --- a/crates/lib/src/ukify.rs +++ b/crates/lib/src/ukify.rs @@ -30,6 +30,7 @@ pub(crate) fn build_ukify( rootfs: &Utf8Path, extra_kargs: &[String], args: &[OsString], + allow_missing_fsverity: bool, ) -> Result<()> { // Warn if --karg is used (temporary workaround) if !extra_kargs.is_empty() { @@ -83,7 +84,11 @@ pub(crate) fn build_ukify( let mut cmdline = crate::bootc_kargs::get_kargs_in_root(&root, std::env::consts::ARCH)?; // Add the composefs digest - let composefs_param = format!("{COMPOSEFS_CMDLINE}={composefs_digest}"); + let composefs_param = if allow_missing_fsverity { + format!("{COMPOSEFS_CMDLINE}=?{composefs_digest}") + } else { + format!("{COMPOSEFS_CMDLINE}={composefs_digest}") + }; cmdline.extend(&Cmdline::from(composefs_param)); // Add any extra kargs provided via --karg @@ -129,7 +134,7 @@ mod tests { let tempdir = tempfile::tempdir().unwrap(); let path = Utf8Path::from_path(tempdir.path()).unwrap(); - let result = build_ukify(path, &[], &[]); + let result = build_ukify(path, &[], &[], false); assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!( @@ -147,7 +152,7 @@ mod tests { fs::create_dir_all(tempdir.path().join("boot/EFI/Linux")).unwrap(); fs::write(tempdir.path().join("boot/EFI/Linux/test.efi"), b"fake uki").unwrap(); - let result = build_ukify(path, &[], &[]); + let result = build_ukify(path, &[], &[], false); assert!(result.is_err()); let err = format!("{:#}", result.unwrap_err()); assert!(