diff --git a/crates/etc-merge/src/lib.rs b/crates/etc-merge/src/lib.rs index 0b78a0af5..1ea0d1f72 100644 --- a/crates/etc-merge/src/lib.rs +++ b/crates/etc-merge/src/lib.rs @@ -794,7 +794,9 @@ pub fn merge( .context("Merging modified files")?; for removed in &diff.removed { - let stat = new_etc_fd.metadata_optional(&removed)?; + // Use symlink_metadata_optional so that symlinks that resolve to a path + // outside the new_etc_fd don't get followed + let stat = new_etc_fd.symlink_metadata_optional(&removed)?; let Some(stat) = stat else { // File/dir doesn't exist in new_etc @@ -1071,6 +1073,12 @@ mod tests { n.create_dir_all("dir/perms")?; n.write("dir/perms/some-file", "Some-file")?; + // File exists in pristine and new_etc (as a symlink pointing outside the root), + // but was deleted in current_etc. The merge should remove it from new_etc + // without following the symlink. + p.write("ext-symlink", "pristine content")?; + symlinkat("/usr/bin/bash", &n, "ext-symlink")?; + let (pristine_etc_files, current_etc_files, new_etc_files) = traverse_etc(&p, &c, Some(&n))?; let diff = compute_diff( @@ -1128,6 +1136,9 @@ mod tests { DIR_BITS | 0o775 ); + // External symlink should be removed without following it + assert!(!n.exists("ext-symlink")); + Ok(()) } diff --git a/crates/lib/src/bootc_composefs/export.rs b/crates/lib/src/bootc_composefs/export.rs index ea760e603..745519a80 100644 --- a/crates/lib/src/bootc_composefs/export.rs +++ b/crates/lib/src/bootc_composefs/export.rs @@ -43,7 +43,7 @@ pub async fn export_repo_to_image( let depl_verity = depl_verity.ok_or_else(|| anyhow::anyhow!("Image {source} not found"))?; - let imginfo = get_imginfo(storage, &depl_verity)?; + let (imginfo, _) = get_imginfo(storage, &depl_verity)?; let config_digest = imginfo.manifest.config().digest().clone(); diff --git a/crates/lib/src/bootc_composefs/status.rs b/crates/lib/src/bootc_composefs/status.rs index c8e7851c3..62e906628 100644 --- a/crates/lib/src/bootc_composefs/status.rs +++ b/crates/lib/src/bootc_composefs/status.rs @@ -7,6 +7,7 @@ use composefs_ctl::composefs::fsverity::Sha512HashValue; use composefs_ctl::composefs_oci; use composefs_oci::OciImage; use fn_error_context::context; +use openssl::sha::Sha256; use serde::{Deserialize, Serialize}; use crate::{ @@ -480,7 +481,10 @@ pub(crate) fn get_bootloader() -> Result { /// Falls back to reading legacy `.imginfo` files for backwards compatibility /// with deployments created before the manifest digest was stored in `.origin`. #[context("Reading image info for deployment {deployment_id}")] -pub(crate) fn get_imginfo(storage: &Storage, deployment_id: &str) -> Result { +pub(crate) fn get_imginfo( + storage: &Storage, + deployment_id: &str, +) -> Result<(ImgConfigManifest, String)> { let ini = read_origin(&storage.physical_root, deployment_id)? .ok_or_else(|| anyhow::anyhow!("No origin file for deployment {deployment_id}"))?; @@ -501,7 +505,7 @@ pub(crate) fn get_imginfo(storage: &Storage, deployment_id: &str) -> Result Result(&buffer) .context("Failed to parse .imginfo file as JSON")?; - Ok(img_conf) + // Compute the manifest digest + let mut hasher = Sha256::new(); + hasher.update(&serde_json::to_vec(&img_conf.manifest).context("Serializing image manifest")?); + let manifest_digest_str = hex::encode(hasher.finish()); + Ok((img_conf, manifest_digest_str)) } #[context("Getting composefs deployment metadata")] @@ -543,9 +551,8 @@ fn boot_entry_from_composefs_deployment( let ostree_img_ref = OstreeImageReference::from_str(&img_name_from_config)?; let img_ref = crate::spec::ImageReference::from(ostree_img_ref); - let img_conf = get_imginfo(storage, &verity)?; + let (img_conf, image_digest) = get_imginfo(storage, &verity)?; - let image_digest = img_conf.manifest.config().digest().to_string(); let architecture = img_conf.config.architecture().to_string(); let version = img_conf .manifest diff --git a/crates/lib/src/bootc_composefs/update.rs b/crates/lib/src/bootc_composefs/update.rs index e0e677958..1f48021c7 100644 --- a/crates/lib/src/bootc_composefs/update.rs +++ b/crates/lib/src/bootc_composefs/update.rs @@ -529,7 +529,7 @@ pub(crate) async fn upgrade_composefs( } if opts.check { - let current_manifest = get_imginfo(storage, &*composefs.cmdline.digest)?; + let (current_manifest, _) = get_imginfo(storage, &*composefs.cmdline.digest)?; let diff = ManifestDiff::new(¤t_manifest.manifest, &img_config.manifest); diff.print(); return Ok(()); diff --git a/tmt/tests/booted/test-loader-entries-source.nu b/tmt/tests/booted/test-loader-entries-source.nu index b52656798..423f80a97 100644 --- a/tmt/tests/booted/test-loader-entries-source.nu +++ b/tmt/tests/booted/test-loader-entries-source.nu @@ -25,6 +25,13 @@ use std assert use tap.nu +let is_bad_version = ostree --version | lines | any {|l| $l | str contains "2026.2" } + +if $is_bad_version { + print "Found Ostree v2026.2, skipping test" + exit 0 +} + def parse_cmdline [] { open /proc/cmdline | str trim | split row " " }