From efdad46dc3589a46c9be68a32cab965a03cc2d49 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 1 Oct 2026 12:19:06 +0530 Subject: [PATCH 1/3] grubcfg: Handle symlink that lead outside of `grub2` We have cases where `grub2/grub.cfg -> ../loader/grub.cfg` and since we moved to cap-std APIs, the metadata gathering for the file fails since the target leads to outside the filesystem (which is the directory `grub2` in this case). To fix, use `std::fs` APIs to get metadata Use `GRUBCONFIG_FILE_MODE` constant instead of raw 0600 mode Signed-off-by: Pragyan Poudyal --- src/aleph.rs | 2 ++ src/grubconfigs.rs | 50 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/aleph.rs b/src/aleph.rs index 783260a7..3ef8ffd4 100644 --- a/src/aleph.rs +++ b/src/aleph.rs @@ -12,6 +12,7 @@ use anyhow::{Context, Result}; use chrono::prelude::*; +use fn_error_context::context; use serde::{Deserialize, Serialize}; use std::fs::File; use std::path::Path; @@ -36,6 +37,7 @@ const ALEPH_PATHS: &[&str] = &[ "sysroot/.bootc-aleph.json", ]; +#[context("Getting aleph version")] pub(crate) fn get_aleph_version(root: &Path) -> Result> { for aleph_path in ALEPH_PATHS { let path = &root.join(aleph_path); diff --git a/src/grubconfigs.rs b/src/grubconfigs.rs index f06180e8..f76a2e4a 100644 --- a/src/grubconfigs.rs +++ b/src/grubconfigs.rs @@ -174,12 +174,19 @@ fn write_grubenv(grubdir: &Dir) -> Result<()> { .run_inherited_with_cmd_context() } -#[context("Ensure file permissions are 0600")] +#[context("Ensure file permissions are {GRUBCONFIG_FILE_MODE:04o}")] fn ensure_file_permissions(target_dir: &Dir, target_file: &str) -> Result<()> { - let metadata = match target_dir.metadata(target_file) { + use std::os::unix::fs::PermissionsExt; + + // grub.cfg may be a symlink pointing outside this Dir cap_std refuses to traverse + // such symlinks via openat, so resolve through /proc/self/fd + let absolute_path = format!("/proc/self/fd/{}/{target_file}", target_dir.as_raw_fd()); + + let metadata = match std::fs::metadata(&absolute_path) { Ok(metadata) => metadata, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { // Silently return if file doesn't exist + log::debug!("File {target_file} not found"); return Ok(()); } Err(e) => return Err(e).context(format!("Reading {} metadata", target_file)), @@ -187,14 +194,21 @@ fn ensure_file_permissions(target_dir: &Dir, target_file: &str) -> Result<()> { let mode = metadata.permissions().mode() & 0o777; if mode != GRUBCONFIG_FILE_MODE { - target_dir - .set_permissions(target_file, Permissions::from_mode(GRUBCONFIG_FILE_MODE)) - .with_context(|| format!("Setting {} permissions to 0600", target_file))?; + std::fs::set_permissions( + absolute_path, + std::fs::Permissions::from_mode(GRUBCONFIG_FILE_MODE), + ) + .with_context(|| { + format!( + "Setting {} permissions to {GRUBCONFIG_FILE_MODE:04o}", + target_file + ) + })?; } Ok(()) } -#[context("Ensure grub files permissions are 0600")] +#[context("Ensure grub files permissions are {GRUBCONFIG_FILE_MODE:04o}")] pub(crate) fn ensure_grub_permissions(grub_dir: &Dir) -> Result<()> { for file_name in GRUB_FILES.iter() { ensure_file_permissions(&grub_dir, file_name) @@ -251,7 +265,7 @@ mod tests { { let metadata = td.metadata("grubenv")?; let mode = metadata.permissions().mode() & 0o777; - assert_eq!(mode, 0o600); + assert_eq!(mode, GRUBCONFIG_FILE_MODE); } Ok(()) } @@ -285,11 +299,29 @@ mod tests { // Verify grubenv was fixed to 0o600 let metadata = grub_dir.metadata("grubenv")?; - assert_eq!(metadata.permissions().mode() & 0o777, 0o600); + assert_eq!(metadata.permissions().mode() & 0o777, GRUBCONFIG_FILE_MODE); // Verify grub.cfg was fixed 0o600 let metadata = grub_dir.metadata("grub.cfg")?; - assert_eq!(metadata.permissions().mode() & 0o777, 0o600); + assert_eq!(metadata.permissions().mode() & 0o777, GRUBCONFIG_FILE_MODE); + + use std::os::unix::fs::PermissionsExt; + + // Test it with symlinks pointing outside the grub2 dir + // Create the symlink target outside the grub2 dir, with wrong perms + let external_target = tdp.join("external-grub.cfg"); + std::fs::write(&external_target, "external grub.cfg content")?; + std::fs::set_permissions(&external_target, std::fs::Permissions::from_mode(0o644))?; + + grub_dir.remove_file("grub.cfg")?; + std::os::unix::fs::symlink(&external_target, grub.join("grub.cfg"))?; + + ensure_grub_permissions(&grub_dir)?; + + // Verify grub.cfg was fixed 0o600 + let metadata = std::fs::metadata(&external_target)?; + assert_eq!(metadata.permissions().mode() & 0o777, GRUBCONFIG_FILE_MODE); + Ok(()) } } From 6969be576cc518159343d05f2a415d5c9f115fb3 Mon Sep 17 00:00:00 2001 From: Pragyan Poudyal Date: Thu, 1 Oct 2026 14:18:27 +0530 Subject: [PATCH 2/3] ci: Skip composefs tests for C10S bootc 1.16.14 (in copr) uses composefs.digest= cmdline, but since we don't rebuild the initramfs, we never include that digest as a condition for bootc-initramfs-setup to run which fails the VM startup Disable C10S tests for composefs, for now Signed-off-by: Pragyan Poudyal --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9c3ab2f..b5be61c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -161,4 +161,10 @@ jobs: run: | set -xeuo pipefail sudo ./scripts/test-uefi-vm-boot.sh "$IMG_NAME" "ostree" - sudo ./scripts/test-uefi-vm-boot.sh "$IMG_NAME" "composefs" + + # TODO: Skip composefs tests for C10S as bootc 1.16.3 uses composefs.digest= + # cmdline, but for some reason that cmdline is not mentioned in + # bootc-root-setup.service + if [[ "${{ matrix.stream }}" != "10" ]]; then + sudo ./scripts/test-uefi-vm-boot.sh "$IMG_NAME" "composefs" + fi From 35948c7ceb2eb667888bac31b0a52a83273cc33c Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sat, 26 Sep 2026 17:12:41 -0400 Subject: [PATCH 3/3] ci: Regenerate the initramfs after installing bootc from copr The CI image replaces the base image's bootc with a newer build from copr but keeps the base image's initramfs, which bootc doesn't support. It broke the stream10 composefs UEFI boot test once bootc started writing only composefs.digest= in BLS entries: the bootc 1.16.13 initramfs in centos-bootc:stream10 only starts bootc-root-setup.service with a composefs= karg, so switch-root fails. Generated-by: AI --- Dockerfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dockerfile b/Dockerfile index 9eabbfb9..ee70610f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,11 @@ if [ "$ID" = "centos" ]; then dnf -y copr enable rhcontainerbot/bootc centos-stream-${VERSION_ID}-$(uname -m) fi dnf -y install bootc +# The copr bootc can be newer than the one in the base image's initramfs; +# a newer bootc with an older initramfs is unsupported (e.g. it can write +# composefs kargs the old initramfs doesn't recognize), so regenerate it. +kver=$(cd /usr/lib/modules && echo *) +dracut --force --kver "$kver" "/usr/lib/modules/$kver/initramfs.img" dnf clean all rm -rf /var/log rm -rf /var/lib