Skip to content

Commit 56d8260

Browse files
Johan-Liebert1cgwalters
authored andcommitted
composefs/gc: Fix shared entry GC even when they're in use
This fixes a bug where a shared Type1 entry would get GCd even when it's in use due to the original image that created it being deleted. Combined with the fact that we were comparing the fsverity digest in the options field of the BLS config (which will be different than the name of the directory containing the vmlinuz + initrd pair). Now, we compare against the directory name when GC-ing boot binaries Fixes: #2102 Also, remove `allow(dead_code)` from BLS and Grub Menuconfig parsers as now we use `boot_artifact_name` method Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 93042c8 commit 56d8260

File tree

4 files changed

+69
-16
lines changed

4 files changed

+69
-16
lines changed

crates/lib/src/bootc_composefs/gc.rs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,11 @@ fn collect_uki_binaries(boot_dir: &Dir, boot_binaries: &mut Vec<BootBinary>) ->
9191
let entry = entry?;
9292
let name = entry.file_name()?;
9393

94-
let Some(verity) = name.strip_prefix(UKI_NAME_PREFIX) else {
94+
let Some(efi_name_no_prefix) = name.strip_prefix(UKI_NAME_PREFIX) else {
9595
continue;
9696
};
9797

98-
if name.ends_with(EFI_EXT) {
98+
if let Some(verity) = efi_name_no_prefix.strip_suffix(EFI_EXT) {
9999
boot_binaries.push((BootType::Uki, verity.into()));
100100
}
101101
}
@@ -235,7 +235,11 @@ pub(crate) async fn composefs_gc(
235235
// filter the ones that are not referenced by any bootloader entry
236236
!bootloader_entries
237237
.iter()
238-
.any(|boot_entry| bin_path.1 == *boot_entry)
238+
// We compare the name of directory containing the binary instead of comparing the
239+
// fsverity digest. This is because a shared entry might differing directory
240+
// name and fsverity digest in the cmdline. And since we want to GC the actual
241+
// binaries, we compare with the directory name
242+
.any(|boot_entry| boot_entry.boot_artifact_name == bin_path.1)
239243
})
240244
.collect::<Vec<_>>();
241245

@@ -263,11 +267,28 @@ pub(crate) async fn composefs_gc(
263267

264268
// Collect the deployments that have an image but no bootloader entry
265269
// and vice versa
266-
let img_bootloader_diff = images
270+
//
271+
// Images without corresponding bootloader entries
272+
let orphaned_images: Vec<&String> = images
267273
.iter()
268-
.filter(|i| !bootloader_entries.contains(i))
269-
.chain(bootloader_entries.iter().filter(|b| !images.contains(b)))
270-
.collect::<Vec<_>>();
274+
.filter(|image| {
275+
!bootloader_entries
276+
.iter()
277+
.any(|entry| &entry.fsverity == *image)
278+
})
279+
.collect();
280+
281+
// Bootloader entries without corresponding images
282+
let orphaned_bootloader_entries: Vec<&String> = bootloader_entries
283+
.iter()
284+
.map(|entry| &entry.fsverity)
285+
.filter(|verity| !images.contains(verity))
286+
.collect();
287+
288+
let img_bootloader_diff: Vec<&String> = orphaned_images
289+
.into_iter()
290+
.chain(orphaned_bootloader_entries)
291+
.collect();
271292

272293
tracing::debug!("img_bootloader_diff: {img_bootloader_diff:#?}");
273294

crates/lib/src/bootc_composefs/status.rs

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,26 @@ pub(crate) struct StagedDeployment {
121121
pub(crate) finalization_locked: bool,
122122
}
123123

124+
#[derive(Debug, PartialEq)]
125+
pub(crate) struct BootloaderEntry {
126+
/// The fsverity digest associated with the bootloader entry
127+
/// This is the value of composefs= param
128+
pub(crate) fsverity: String,
129+
/// The name of the (UKI/Kernel+Initrd directory) related to the entry
130+
///
131+
/// For UKI, this is the name of the UKI stripped of our custom
132+
/// prefix and .efi suffix
133+
///
134+
/// For Type1 entries, this is the name to the directory containing
135+
/// Kernel+Initrd, stripped of our custom prefix
136+
///
137+
/// Since this is stripped of all our custom prefixes + file extensions
138+
/// this is basically the verity digest part of the name
139+
///
140+
/// We mainly need this in order to GC shared Type1 entries
141+
pub(crate) boot_artifact_name: String,
142+
}
143+
124144
/// Detect if we have `composefs=<digest>` in `/proc/cmdline`
125145
pub(crate) fn composefs_booted() -> Result<Option<&'static ComposefsCmdline>> {
126146
static CACHED_DIGEST_VALUE: OnceLock<Option<ComposefsCmdline>> = OnceLock::new();
@@ -263,7 +283,7 @@ fn get_sorted_type1_boot_entries_helper(
263283
Ok(all_configs)
264284
}
265285

266-
fn list_type1_entries(boot_dir: &Dir) -> Result<Vec<String>> {
286+
fn list_type1_entries(boot_dir: &Dir) -> Result<Vec<BootloaderEntry>> {
267287
// Type1 Entry
268288
let boot_entries = get_sorted_type1_boot_entries(boot_dir, true)?;
269289

@@ -274,7 +294,12 @@ fn list_type1_entries(boot_dir: &Dir) -> Result<Vec<String>> {
274294
boot_entries
275295
.into_iter()
276296
.chain(staged_boot_entries)
277-
.map(|entry| entry.get_verity())
297+
.map(|entry| {
298+
Ok(BootloaderEntry {
299+
fsverity: entry.get_verity()?,
300+
boot_artifact_name: entry.boot_artifact_name()?.to_string(),
301+
})
302+
})
278303
.collect::<Result<Vec<_>, _>>()
279304
}
280305

@@ -283,7 +308,7 @@ fn list_type1_entries(boot_dir: &Dir) -> Result<Vec<String>> {
283308
/// # Returns
284309
/// The fsverity of EROFS images corresponding to boot entries
285310
#[fn_error_context::context("Listing bootloader entries")]
286-
pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result<Vec<String>> {
311+
pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result<Vec<BootloaderEntry>> {
287312
let bootloader = get_bootloader()?;
288313
let boot_dir = storage.require_boot_dir()?;
289314

@@ -304,8 +329,13 @@ pub(crate) fn list_bootloader_entries(storage: &Storage) -> Result<Vec<String>>
304329
boot_entries
305330
.into_iter()
306331
.chain(boot_entries_staged)
307-
.map(|entry| entry.get_verity())
308-
.collect::<Result<Vec<_>, _>>()?
332+
.map(|entry| {
333+
Ok(BootloaderEntry {
334+
fsverity: entry.get_verity()?,
335+
boot_artifact_name: entry.boot_artifact_name()?,
336+
})
337+
})
338+
.collect::<Result<Vec<_>, anyhow::Error>>()?
309339
} else {
310340
list_type1_entries(boot_dir)?
311341
}
@@ -739,7 +769,11 @@ async fn composefs_deployment_status_from(
739769
// Rollback deployment is in here, but may also contain stale deployment entries
740770
let mut extra_deployment_boot_entries: Vec<BootEntry> = Vec::new();
741771

742-
for verity_digest in bootloader_entry_verity {
772+
for BootloaderEntry {
773+
fsverity: verity_digest,
774+
..
775+
} in bootloader_entry_verity
776+
{
743777
// read the origin file
744778
let config = state_dir
745779
.open_dir(&verity_digest)
@@ -877,6 +911,7 @@ async fn composefs_deployment_status_from(
877911
.map(|menu| menu.get_verity()),
878912
)
879913
.collect::<Result<HashSet<_>>>()?;
914+
880915
let rollback_candidates: Vec<_> = extra_deployment_boot_entries
881916
.into_iter()
882917
.filter(|entry| {

crates/lib/src/parsers/bls_config.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ impl BLSConfig {
211211
///
212212
/// The names are stripped of our custom prefix and suffixes, so this returns the
213213
/// verity digest part of the name
214-
#[allow(dead_code)]
215214
pub(crate) fn boot_artifact_name(&self) -> Result<&str> {
216215
match &self.cfg_type {
217216
BLSConfigType::EFI { efi } => {

crates/lib/src/parsers/grub_menuconfig.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
//! Parser for GRUB menuentry configuration files using nom combinators.
22
3-
#![allow(dead_code)]
4-
53
use std::fmt::Display;
64

75
use anyhow::Result;

0 commit comments

Comments
 (0)