Skip to content

Commit cae6f03

Browse files
composefs/soft-reboot: Handle soft reboot for UKIs
Similar to soft reboots for Type1 entries, we compute the SHA256Sum of .linux + .initrd sections in the UKI, and compare them to check for kernel skew Next, compare the .cmdline section skipping the `composefs=` parameter as that will always be different Signed-off-by: Pragyan Poudyal <pragyanpoudyal41999@gmail.com>
1 parent 123e5b3 commit cae6f03

File tree

5 files changed

+210
-80
lines changed

5 files changed

+210
-80
lines changed

crates/lib/src/bootc_composefs/boot.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,8 @@ use cap_std_ext::{
7979
use clap::ValueEnum;
8080
use composefs::fs::read_file;
8181
use composefs::tree::RegularFile;
82+
use composefs_boot::bootloader::{PEType, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT};
8283
use composefs_boot::BootOps;
83-
use composefs_boot::{
84-
bootloader::{PEType, EFI_ADDON_DIR_EXT, EFI_ADDON_FILE_EXT, EFI_EXT},
85-
uki::UkiError,
86-
};
8784
use fn_error_context::context;
8885
use ostree_ext::composefs::fsverity::{FsVerityHashValue, Sha512HashValue};
8986
use ostree_ext::composefs_boot::bootloader::UsrLibModulesVmlinuz;
@@ -840,8 +837,7 @@ fn write_pe_to_esp(
840837
);
841838
}
842839

843-
let osrel = uki::get_text_section(&efi_bin, ".osrel")
844-
.ok_or(UkiError::PortableExecutableError)??;
840+
let osrel = uki::get_text_section(&efi_bin, ".osrel")?;
845841

846842
let parsed_osrel = OsReleaseInfo::parse(osrel);
847843

crates/lib/src/bootc_composefs/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pub(crate) mod state;
1010
pub(crate) mod status;
1111
pub(crate) mod switch;
1212
pub(crate) mod update;
13+
pub(crate) mod utils;

crates/lib/src/bootc_composefs/state.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use std::io::Write;
2-
use std::ops::Deref;
32
use std::os::unix::fs::symlink;
43
use std::path::Path;
54
use std::{fs::create_dir_all, process::Command};
@@ -108,20 +107,22 @@ pub(crate) fn copy_etc_to_state(
108107
cp_ret
109108
}
110109

111-
/// Updates the currently booted image's target imgref
112-
pub(crate) fn update_target_imgref_in_origin(
110+
/// Adds or updates the provided key/value pairs in the .origin file of the deployment pointed to
111+
/// by the `deployment_id`
112+
fn add_update_in_origin(
113113
storage: &Storage,
114-
booted_cfs: &BootedComposefs,
115-
imgref: &ImageReference,
114+
deployment_id: &str,
115+
section: &str,
116+
kv_pairs: &[(&str, &str)],
116117
) -> Result<()> {
117-
let path = Path::new(STATE_DIR_RELATIVE).join(booted_cfs.cmdline.digest.deref());
118+
let path = Path::new(STATE_DIR_RELATIVE).join(deployment_id);
118119

119120
let state_dir = storage
120121
.physical_root
121122
.open_dir(path)
122123
.context("Opening state dir")?;
123124

124-
let origin_filename = format!("{}.origin", booted_cfs.cmdline.digest.deref());
125+
let origin_filename = format!("{deployment_id}.origin");
125126

126127
let origin_file = state_dir
127128
.read_to_string(&origin_filename)
@@ -130,11 +131,9 @@ pub(crate) fn update_target_imgref_in_origin(
130131
let mut ini =
131132
tini::Ini::from_string(&origin_file).context("Failed to parse file origin file as ini")?;
132133

133-
// Replace the origin
134-
ini = ini.section("origin").item(
135-
ORIGIN_CONTAINER,
136-
format!("ostree-unverified-image:{imgref}"),
137-
);
134+
for (key, value) in kv_pairs {
135+
ini = ini.section(section).item(*key, *value);
136+
}
138137

139138
state_dir
140139
.atomic_replace_with(origin_filename, move |f| -> std::io::Result<_> {
@@ -151,6 +150,36 @@ pub(crate) fn update_target_imgref_in_origin(
151150
Ok(())
152151
}
153152

153+
/// Updates the currently booted image's target imgref
154+
pub(crate) fn update_target_imgref_in_origin(
155+
storage: &Storage,
156+
booted_cfs: &BootedComposefs,
157+
imgref: &ImageReference,
158+
) -> Result<()> {
159+
add_update_in_origin(
160+
storage,
161+
booted_cfs.cmdline.digest.as_ref(),
162+
"origin",
163+
&[(
164+
ORIGIN_CONTAINER,
165+
&format!("ostree-unverified-image:{imgref}"),
166+
)],
167+
)
168+
}
169+
170+
pub(crate) fn update_boot_digest_in_origin(
171+
storage: &Storage,
172+
digest: &str,
173+
boot_digest: &str,
174+
) -> Result<()> {
175+
add_update_in_origin(
176+
storage,
177+
digest,
178+
ORIGIN_KEY_BOOT,
179+
&[(ORIGIN_KEY_BOOT_DIGEST, boot_digest)],
180+
)
181+
}
182+
154183
/// Creates and populates the composefs state directory for a deployment.
155184
///
156185
/// This function sets up the state directory structure and configuration files
@@ -184,7 +213,7 @@ pub(crate) async fn write_composefs_state(
184213
target_imgref: &ImageReference,
185214
staged: bool,
186215
boot_type: BootType,
187-
boot_digest: Option<String>,
216+
boot_digest: String,
188217
container_details: &ImgConfigManifest,
189218
) -> Result<()> {
190219
let state_path = root_path
@@ -223,11 +252,9 @@ pub(crate) async fn write_composefs_state(
223252
.section(ORIGIN_KEY_BOOT)
224253
.item(ORIGIN_KEY_BOOT_TYPE, boot_type);
225254

226-
if let Some(boot_digest) = boot_digest {
227-
config = config
228-
.section(ORIGIN_KEY_BOOT)
229-
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
230-
}
255+
config = config
256+
.section(ORIGIN_KEY_BOOT)
257+
.item(ORIGIN_KEY_BOOT_DIGEST, boot_digest);
231258

232259
let state_dir =
233260
Dir::open_ambient_dir(&state_path, ambient_authority()).context("Opening state dir")?;

crates/lib/src/bootc_composefs/status.rs

Lines changed: 104 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ use fn_error_context::context;
77
use serde::{Deserialize, Serialize};
88

99
use crate::{
10-
bootc_composefs::{boot::BootType, repo::get_imgref},
10+
bootc_composefs::{
11+
boot::BootType,
12+
repo::get_imgref,
13+
utils::{compute_store_boot_digest_for_uki, get_uki_cmdline},
14+
},
1115
composefs_consts::{
1216
COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT_DIGEST, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG,
1317
},
@@ -350,26 +354,33 @@ pub(crate) async fn get_composefs_status(
350354
composefs_deployment_status_from(&storage, booted_cfs.cmdline).await
351355
}
352356

353-
fn set_soft_reboot_capable_bls(
357+
/// Check whether any deployment is capable of being soft rebooted or not
358+
#[context("Checking soft reboot capability")]
359+
fn set_soft_reboot_capability(
354360
storage: &Storage,
355361
host: &mut Host,
356-
bls_entries: &Vec<BLSConfig>,
362+
bls_entries: Option<Vec<BLSConfig>>,
357363
cmdline: &ComposefsCmdline,
358364
) -> Result<()> {
359365
let booted = host.require_composefs_booted()?;
360366

361367
match booted.boot_type {
362368
BootType::Bls => {
363-
set_reboot_capable_type1_deployments(storage, cmdline, host, bls_entries)?;
364-
}
369+
let mut bls_entries =
370+
bls_entries.ok_or_else(|| anyhow::anyhow!("BLS entries not provided"))?;
365371

366-
BootType::Uki => match booted.bootloader {
367-
Bootloader::Grub => todo!(),
368-
Bootloader::Systemd => todo!(),
369-
},
370-
};
372+
let staged_entries =
373+
get_sorted_staged_type1_boot_entries(storage.require_boot_dir()?, false)?;
371374

372-
Ok(())
375+
// We will have a duplicate booted entry here, but that's fine as we only use this
376+
// vector to check for existence of an entry
377+
bls_entries.extend(staged_entries);
378+
379+
set_reboot_capable_type1_deployments(cmdline, host, bls_entries)
380+
}
381+
382+
BootType::Uki => set_reboot_capable_uki_deployments(storage, cmdline, host),
383+
}
373384
}
374385

375386
fn find_bls_entry<'a>(
@@ -406,73 +417,112 @@ fn compare_cmdline_skip_cfs(first: &Cmdline<'_>, second: &Cmdline<'_>) -> bool {
406417
return true;
407418
}
408419

409-
fn set_soft_reboot_capable_type1(
410-
deployment: &mut BootEntry,
411-
bls_entries: &Vec<BLSConfig>,
412-
booted_bls_entry: &BLSConfig,
413-
booted_boot_digest: &String,
420+
#[context("Setting soft reboot capability for Type1 entries")]
421+
fn set_reboot_capable_type1_deployments(
422+
booted_cmdline: &ComposefsCmdline,
423+
host: &mut Host,
424+
bls_entries: Vec<BLSConfig>,
414425
) -> Result<()> {
415-
let deployment_cfs = deployment.require_composefs()?;
426+
let booted = host
427+
.status
428+
.booted
429+
.as_ref()
430+
.ok_or_else(|| anyhow::anyhow!("Failed to find booted entry"))?;
431+
432+
let booted_boot_digest = booted.composefs_boot_digest()?;
433+
434+
let booted_bls_entry = find_bls_entry(&*booted_cmdline.digest, &bls_entries)?
435+
.ok_or_else(|| anyhow::anyhow!("Booted BLS entry not found"))?;
416436

417-
// TODO: Unwrap
418-
if deployment_cfs.boot_digest.as_ref().unwrap() != booted_boot_digest {
419-
deployment.soft_reboot_capable = false;
420-
return Ok(());
437+
let booted_cmdline = booted_bls_entry.get_cmdline()?;
438+
439+
for depl in host
440+
.status
441+
.staged
442+
.iter_mut()
443+
.chain(host.status.rollback.iter_mut())
444+
.chain(host.status.other_deployments.iter_mut())
445+
{
446+
let entry = find_bls_entry(&depl.require_composefs()?.verity, &bls_entries)?
447+
.ok_or_else(|| anyhow::anyhow!("Entry not found"))?;
448+
449+
let depl_cmdline = entry.get_cmdline()?;
450+
451+
depl.soft_reboot_capable = is_soft_rebootable(
452+
depl.composefs_boot_digest()?,
453+
booted_boot_digest,
454+
depl_cmdline,
455+
booted_cmdline,
456+
);
421457
}
422458

423-
let entry = find_bls_entry(&deployment_cfs.verity, bls_entries)?
424-
.ok_or_else(|| anyhow::anyhow!("Entry not found"))?;
459+
Ok(())
460+
}
425461

426-
let opts = entry.get_cmdline()?;
427-
let booted_cmdline_opts = booted_bls_entry.get_cmdline()?;
462+
fn is_soft_rebootable(
463+
depl_boot_digest: &str,
464+
booted_boot_digest: &str,
465+
depl_cmdline: &Cmdline,
466+
booted_cmdline: &Cmdline,
467+
) -> bool {
468+
if depl_boot_digest != booted_boot_digest {
469+
tracing::debug!("Soft reboot not allowed due to kernel skew");
470+
return false;
471+
}
428472

429-
if opts.len() != booted_cmdline_opts.len() {
473+
if depl_cmdline.as_bytes().len() != booted_cmdline.as_bytes().len() {
430474
tracing::debug!("Soft reboot not allowed due to differing cmdline");
431-
deployment.soft_reboot_capable = false;
432-
return Ok(());
475+
return false;
433476
}
434477

435-
deployment.soft_reboot_capable = compare_cmdline_skip_cfs(opts, booted_cmdline_opts)
436-
&& compare_cmdline_skip_cfs(booted_cmdline_opts, opts);
437-
438-
return Ok(());
478+
return compare_cmdline_skip_cfs(depl_cmdline, booted_cmdline)
479+
&& compare_cmdline_skip_cfs(booted_cmdline, depl_cmdline);
439480
}
440481

441-
fn set_reboot_capable_type1_deployments(
482+
#[context("Setting soft reboot capability for UKI deployments")]
483+
fn set_reboot_capable_uki_deployments(
442484
storage: &Storage,
443485
cmdline: &ComposefsCmdline,
444486
host: &mut Host,
445-
bls_entries: &Vec<BLSConfig>,
446487
) -> Result<()> {
447488
let booted = host
448489
.status
449490
.booted
450491
.as_ref()
451492
.ok_or_else(|| anyhow::anyhow!("Failed to find booted entry"))?;
452493

453-
let booted_boot_digest = booted.composefs_boot_digest()?;
454-
455-
let booted_bls_entry = find_bls_entry(&*cmdline.digest, bls_entries)?
456-
.ok_or_else(|| anyhow::anyhow!("Booted bls entry not found"))?;
494+
// Since older booted systems won't have the boot digest for UKIs
495+
let booted_boot_digest = match booted.composefs_boot_digest() {
496+
Ok(d) => d,
497+
Err(_) => &compute_store_boot_digest_for_uki(storage, &cmdline.digest)?,
498+
};
457499

458-
if let Some(staged) = host.status.staged.as_mut() {
459-
let staged_entries =
460-
get_sorted_staged_type1_boot_entries(storage.require_boot_dir()?, true)?;
500+
let booted_cmdline = get_uki_cmdline(storage, &booted.require_composefs()?.verity)?;
461501

462-
set_soft_reboot_capable_type1(
463-
staged,
464-
&staged_entries,
465-
booted_bls_entry,
466-
booted_boot_digest,
467-
)?;
468-
}
502+
for deployment in host
503+
.status
504+
.staged
505+
.iter_mut()
506+
.chain(host.status.rollback.iter_mut())
507+
.chain(host.status.other_deployments.iter_mut())
508+
{
509+
// Since older booted systems won't have the boot digest for UKIs
510+
let depl_boot_digest = match deployment.composefs_boot_digest() {
511+
Ok(d) => d,
512+
Err(_) => &compute_store_boot_digest_for_uki(
513+
storage,
514+
&deployment.require_composefs()?.verity,
515+
)?,
516+
};
469517

470-
if let Some(rollback) = &mut host.status.rollback {
471-
set_soft_reboot_capable_type1(rollback, bls_entries, booted_bls_entry, booted_boot_digest)?;
472-
}
518+
let depl_cmdline = get_uki_cmdline(storage, &deployment.require_composefs()?.verity)?;
473519

474-
for depl in &mut host.status.other_deployments {
475-
set_soft_reboot_capable_type1(depl, bls_entries, booted_bls_entry, booted_boot_digest)?;
520+
deployment.soft_reboot_capable = is_soft_rebootable(
521+
depl_boot_digest,
522+
booted_boot_digest,
523+
&depl_cmdline,
524+
&booted_cmdline,
525+
);
476526
}
477527

478528
Ok(())
@@ -645,9 +695,7 @@ pub(crate) async fn composefs_deployment_status_from(
645695
host.spec.boot_order = BootOrder::Rollback
646696
};
647697

648-
if let Some(bls_configs) = sorted_bls_config {
649-
set_soft_reboot_capable_bls(storage, &mut host, &bls_configs, cmdline)?;
650-
}
698+
set_soft_reboot_capability(storage, &mut host, sorted_bls_config, cmdline)?;
651699

652700
Ok(host)
653701
}

0 commit comments

Comments
 (0)