Skip to content

Commit 1670cdc

Browse files
chaserhkjJohan-Liebert1
authored andcommitted
composefs/status: resolve rollback entry correctly
Previous implementation had undefined behavior and was coincidentally correct under conditions where no rollback was performed, see #1887 Matches deployment entries in composefs deploy folder that are neither staged nor booted against entires defined in /boot to find out rollback entry. Fixes #1887 Signed-off-by: Chaser Huang <[email protected]>
1 parent ad60763 commit 1670cdc

File tree

1 file changed

+43
-8
lines changed

1 file changed

+43
-8
lines changed

crates/lib/src/bootc_composefs/status.rs

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::{io::Read, sync::OnceLock};
1+
use std::{collections::HashSet, io::Read, sync::OnceLock};
22

33
use anyhow::{Context, Result};
44
use bootc_kernel_cmdline::utf8::Cmdline;
@@ -570,6 +570,10 @@ pub(crate) async fn composefs_deployment_status_from(
570570
// NOTE: This cannot work if we support both BLS and UKI at the same time
571571
let mut boot_type: Option<BootType> = None;
572572

573+
// Boot entries from deployments that are neither booted nor staged deployments
574+
// Rollback deployment is in here, but may also contain stale deployment entries
575+
let mut extra_deployment_boot_entries: Vec<BootEntry> = Vec::new();
576+
573577
for depl in deployments {
574578
let depl = depl?;
575579

@@ -617,7 +621,7 @@ pub(crate) async fn composefs_deployment_status_from(
617621
}
618622
}
619623

620-
host.status.rollback = Some(boot_entry);
624+
extra_deployment_boot_entries.push(boot_entry);
621625
}
622626

623627
// Shouldn't really happen, but for sanity nonetheless
@@ -627,7 +631,8 @@ pub(crate) async fn composefs_deployment_status_from(
627631

628632
let booted_cfs = host.require_composefs_booted()?;
629633

630-
let (is_rollback_queued, sorted_bls_config) = match booted_cfs.bootloader {
634+
let mut grub_menu_string = String::new();
635+
let (is_rollback_queued, sorted_bls_config, grub_menu_entries) = match booted_cfs.bootloader {
631636
Bootloader::Grub => match boot_type {
632637
BootType::Bls => {
633638
let bls_configs = get_sorted_type1_boot_entries(boot_dir, false)?;
@@ -642,7 +647,7 @@ pub(crate) async fn composefs_deployment_status_from(
642647
.ok_or_else(|| anyhow::anyhow!("options key not found in bls config"))?
643648
.contains(booted_composefs_digest.as_ref());
644649

645-
(is_rollback_queued, Some(bls_configs))
650+
(is_rollback_queued, Some(bls_configs), None)
646651
}
647652

648653
BLSConfigType::EFI { .. } => {
@@ -654,8 +659,8 @@ pub(crate) async fn composefs_deployment_status_from(
654659
}
655660

656661
BootType::Uki => {
657-
let mut s = String::new();
658-
let menuentries = get_sorted_grub_uki_boot_entries(boot_dir, &mut s)?;
662+
let menuentries =
663+
get_sorted_grub_uki_boot_entries(boot_dir, &mut grub_menu_string)?;
659664

660665
let is_rollback_queued = !menuentries
661666
.first()
@@ -664,7 +669,7 @@ pub(crate) async fn composefs_deployment_status_from(
664669
.chainloader
665670
.contains(booted_composefs_digest.as_ref());
666671

667-
(is_rollback_queued, None)
672+
(is_rollback_queued, None, Some(menuentries))
668673
}
669674
},
670675

@@ -690,10 +695,40 @@ pub(crate) async fn composefs_deployment_status_from(
690695
BLSConfigType::Unknown => anyhow::bail!("Unknown BLS Config Type"),
691696
};
692697

693-
(is_rollback_queued, Some(bls_configs))
698+
(is_rollback_queued, Some(bls_configs), None)
694699
}
695700
};
696701

702+
// Determine rollback deployment by matching extra deployment boot entries against entires read from /boot
703+
// This collects verity digest across bls and grub enties, we should just have one of them, but still works
704+
let bootloader_configured_verity = sorted_bls_config
705+
.iter()
706+
.flatten()
707+
.map(|cfg| cfg.get_verity())
708+
.chain(
709+
grub_menu_entries
710+
.iter()
711+
.flatten()
712+
.map(|menu| menu.get_verity()),
713+
)
714+
.collect::<Result<HashSet<_>>>()?;
715+
for entry in extra_deployment_boot_entries {
716+
// SAFETY: boot_entry.composefs will always be present
717+
let verity = &entry.composefs.as_ref().unwrap().verity;
718+
if bootloader_configured_verity.contains(verity) {
719+
match host.status.rollback {
720+
Some(ref _entry) => {
721+
anyhow::bail!(
722+
"Multiple extra entires in /boot, could not determine rollback entry"
723+
);
724+
}
725+
None => {
726+
host.status.rollback = Some(entry);
727+
}
728+
}
729+
}
730+
}
731+
697732
host.status.rollback_queued = is_rollback_queued;
698733

699734
if host.status.rollback_queued {

0 commit comments

Comments
 (0)