Skip to content

Commit 3479a49

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 3479a49

File tree

5 files changed

+208
-80
lines changed

5 files changed

+208
-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: 102 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ 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, repo::get_imgref, utils::{compute_store_boot_digest_for_uki, get_uki_cmdline}
12+
},
1113
composefs_consts::{
1214
COMPOSEFS_CMDLINE, ORIGIN_KEY_BOOT_DIGEST, TYPE1_ENT_PATH, TYPE1_ENT_PATH_STAGED, USER_CFG,
1315
},
@@ -350,26 +352,33 @@ pub(crate) async fn get_composefs_status(
350352
composefs_deployment_status_from(&storage, booted_cfs.cmdline).await
351353
}
352354

353-
fn set_soft_reboot_capable_bls(
355+
/// Check whether any deployment is capable of being soft rebooted or not
356+
#[context("Checking soft reboot capability")]
357+
fn set_soft_reboot_capability(
354358
storage: &Storage,
355359
host: &mut Host,
356-
bls_entries: &Vec<BLSConfig>,
360+
bls_entries: Option<Vec<BLSConfig>>,
357361
cmdline: &ComposefsCmdline,
358362
) -> Result<()> {
359363
let booted = host.require_composefs_booted()?;
360364

361365
match booted.boot_type {
362366
BootType::Bls => {
363-
set_reboot_capable_type1_deployments(storage, cmdline, host, bls_entries)?;
364-
}
367+
let mut bls_entries =
368+
bls_entries.ok_or_else(|| anyhow::anyhow!("BLS entries not provided"))?;
365369

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

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

375384
fn find_bls_entry<'a>(
@@ -406,73 +415,112 @@ fn compare_cmdline_skip_cfs(first: &Cmdline<'_>, second: &Cmdline<'_>) -> bool {
406415
return true;
407416
}
408417

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,
418+
#[context("Setting soft reboot capability for Type1 entries")]
419+
fn set_reboot_capable_type1_deployments(
420+
booted_cmdline: &ComposefsCmdline,
421+
host: &mut Host,
422+
bls_entries: Vec<BLSConfig>,
414423
) -> Result<()> {
415-
let deployment_cfs = deployment.require_composefs()?;
424+
let booted = host
425+
.status
426+
.booted
427+
.as_ref()
428+
.ok_or_else(|| anyhow::anyhow!("Failed to find booted entry"))?;
429+
430+
let booted_boot_digest = booted.composefs_boot_digest()?;
431+
432+
let booted_bls_entry = find_bls_entry(&*booted_cmdline.digest, &bls_entries)?
433+
.ok_or_else(|| anyhow::anyhow!("Booted BLS entry not found"))?;
416434

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

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

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

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

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(());
476+
return compare_cmdline_skip_cfs(depl_cmdline, booted_cmdline)
477+
&& compare_cmdline_skip_cfs(booted_cmdline, depl_cmdline);
439478
}
440479

441-
fn set_reboot_capable_type1_deployments(
480+
#[context("Setting soft reboot capability for UKI deployments")]
481+
fn set_reboot_capable_uki_deployments(
442482
storage: &Storage,
443483
cmdline: &ComposefsCmdline,
444484
host: &mut Host,
445-
bls_entries: &Vec<BLSConfig>,
446485
) -> Result<()> {
447486
let booted = host
448487
.status
449488
.booted
450489
.as_ref()
451490
.ok_or_else(|| anyhow::anyhow!("Failed to find booted entry"))?;
452491

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"))?;
492+
// Since older booted systems won't have the boot digest for UKIs
493+
let booted_boot_digest = match booted.composefs_boot_digest() {
494+
Ok(d) => d,
495+
Err(_) => &compute_store_boot_digest_for_uki(storage, &cmdline.digest)?,
496+
};
457497

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)?;
498+
let booted_cmdline = get_uki_cmdline(storage, &booted.require_composefs()?.verity)?;
461499

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

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-
}
516+
let depl_cmdline = get_uki_cmdline(storage, &deployment.require_composefs()?.verity)?;
473517

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

478526
Ok(())
@@ -645,9 +693,7 @@ pub(crate) async fn composefs_deployment_status_from(
645693
host.spec.boot_order = BootOrder::Rollback
646694
};
647695

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

652698
Ok(host)
653699
}

0 commit comments

Comments
 (0)