summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHermes Agent <hermes@localhost>2026-08-12 06:49:50 +0000
committerHermes Agent <hermes@localhost>2026-08-12 06:49:50 +0000
commitec74c27faad3e65782ae5bb51a8e8e5b629f42ec (patch)
tree00be820298f3633e33401c4ce9ea94a58ebc167d
parent5a4c5671ada1f78c23dd5589765e44a64048f203 (diff)
Create protected boot aliases after initramfs build
-rw-r--r--src/initramfs.rs29
-rw-r--r--tests/initramfs.rs25
2 files changed, 53 insertions, 1 deletions
diff --git a/src/initramfs.rs b/src/initramfs.rs
index ca1063d..4475aed 100644
--- a/src/initramfs.rs
+++ b/src/initramfs.rs
@@ -1,5 +1,6 @@
use std::ffi::OsString;
use std::fs;
+use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::Command;
@@ -171,10 +172,36 @@ impl<R: CommandRunner> InitramfsBuilder for MakeInitrdBuilder<R> {
request.kernel().as_str().into(),
],
))?;
- InitramfsResult::from_rootfs(request.rootfs(), request.kernel())
+ let result = InitramfsResult::from_rootfs(request.rootfs(), request.kernel())?;
+ write_boot_alias(
+ request.rootfs(),
+ "vmlinuz",
+ Path::new(&format!("vmlinuz-{}", request.kernel().as_str())),
+ )?;
+ write_boot_alias(
+ request.rootfs(),
+ "initrd.img",
+ Path::new(result.initrd_path().file_name().expect("initrd filename")),
+ )?;
+ Ok(result)
}
}
+/// Set a conventional boot alias without ever following or replacing a
+/// pre-existing non-symlink filesystem entry.
+fn write_boot_alias(rootfs: &Path, alias: &str, target: &Path) -> Result<()> {
+ let path = rootfs.join("boot").join(alias);
+ match fs::symlink_metadata(&path) {
+ Ok(metadata) if metadata.file_type().is_symlink() => {
+ fs::remove_file(&path).with_context(|| format!("remove existing boot alias {}", path.display()))?;
+ }
+ Ok(_) => bail!("refusing to replace non-symlink boot alias: {}", path.display()),
+ Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
+ Err(error) => return Err(error).with_context(|| format!("inspect boot alias {}", path.display())),
+ }
+ symlink(target, &path).with_context(|| format!("create boot alias {}", path.display()))
+}
+
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InitramfsResult {
initrd_path: PathBuf,
diff --git a/tests/initramfs.rs b/tests/initramfs.rs
index f5a7b02..429411a 100644
--- a/tests/initramfs.rs
+++ b/tests/initramfs.rs
@@ -83,6 +83,31 @@ fn make_initrd_adapter_uses_typed_chroot_arguments_and_records_output_digest() {
result.sha256(),
"aec42bc86f526931d7c7f01ecba8d643dbb748f9102539a99db5d4855ee4435f"
);
+ assert_eq!(
+ fs::read_link(rootfs.path().join("boot/vmlinuz")).expect("kernel boot symlink"),
+ Path::new("vmlinuz-6.12-rt1")
+ );
+ assert_eq!(
+ fs::read_link(rootfs.path().join("boot/initrd.img")).expect("initrd boot symlink"),
+ Path::new("initrd-6.12-rt1.img")
+ );
+}
+
+#[test]
+fn initramfs_adapter_refuses_to_replace_a_non_symlink_boot_alias() {
+ let rootfs = tempdir().expect("rootfs directory");
+ let boot = rootfs.path().join("boot");
+ fs::create_dir(&boot).expect("boot directory");
+ fs::write(boot.join("vmlinuz-6.12-rt1"), "kernel").expect("RT kernel");
+ fs::write(boot.join("initrd-6.12-rt1.img"), b"generated initrd").expect("generated initrd");
+ fs::write(boot.join("initrd.img"), "do not replace").expect("protected boot alias");
+ let request = InitramfsRequest::discover(rootfs.path()).expect("initramfs request");
+ let mut builder = MakeInitrdBuilder::new(RecordingRunner::default());
+
+ let error = builder.build(&request).expect_err("regular boot alias must be protected");
+
+ assert!(error.to_string().contains("refusing to replace non-symlink boot alias"));
+ assert_eq!(fs::read_to_string(boot.join("initrd.img")).expect("protected alias"), "do not replace");
}
#[derive(Debug, Default)]