diff options
| author | Hermes Agent <hermes@localhost> | 2026-08-12 06:49:50 +0000 |
|---|---|---|
| committer | Hermes Agent <hermes@localhost> | 2026-08-12 06:49:50 +0000 |
| commit | ec74c27faad3e65782ae5bb51a8e8e5b629f42ec (patch) | |
| tree | 00be820298f3633e33401c4ce9ea94a58ebc167d | |
| parent | 5a4c5671ada1f78c23dd5589765e44a64048f203 (diff) | |
Create protected boot aliases after initramfs build
| -rw-r--r-- | src/initramfs.rs | 29 | ||||
| -rw-r--r-- | tests/initramfs.rs | 25 |
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)] |