Skip to content

Commit fca07ea

Browse files
committed
ephemeral: Support extracting UKIs
We want `bcvk ephemeral` to work for systems with UKIs; there's two choices here. We could switch in this case to actually booting via EFI firmware, and rely on the systemd-stub credentials to do things like inject kargs. But here I chose to do something a bit simpler - we extract the kernel+initramfs via `objcopy` from the UKI and use those in exactly the same way we do things today. Signed-off-by: Colin Walters <[email protected]>
1 parent 84b02c2 commit fca07ea

File tree

2 files changed

+86
-52
lines changed

2 files changed

+86
-52
lines changed

crates/kit/src/qemu.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ impl Default for ResourceLimits {
115115
#[derive(Debug)]
116116
pub enum BootMode {
117117
/// Direct kernel boot (fast, testing-focused)
118+
/// Also used for UKI boot after extracting kernel/initramfs from UKI PE sections
118119
DirectBoot {
119120
kernel_path: String,
120121
initramfs_path: String,

crates/kit/src/run_ephemeral.rs

Lines changed: 85 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -737,70 +737,102 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> {
737737
};
738738
tracing::debug!("Target image has cloud-init: {cloudinit}");
739739

740-
// Find kernel and initramfs from the container image (not the host)
740+
// Verify KVM access
741+
if !Utf8Path::new("/dev/kvm").exists() || !fs::File::open("/dev/kvm").is_ok() {
742+
return Err(eyre!("KVM device not accessible"));
743+
}
744+
745+
// Create QEMU mount points
746+
fs::create_dir_all("/run/qemu")?;
747+
748+
// Find kernel and initramfs in /usr/lib/modules/
741749
let modules_dir = Utf8Path::new("/run/source-image/usr/lib/modules");
750+
let mut uki_file = None;
742751
let mut vmlinuz_path = None;
743752
let mut initramfs_path = None;
744753

745754
for entry in fs::read_dir(modules_dir)? {
746755
let entry = entry?;
747-
let kernel_dir = entry.path();
748-
if kernel_dir.is_dir() {
749-
let vmlinuz = kernel_dir.join("vmlinuz");
750-
let initramfs = kernel_dir.join("initramfs.img");
756+
let path = entry.path();
757+
758+
// Check for UKI (.efi file)
759+
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("efi") {
760+
debug!("Found UKI file: {:?}", path);
761+
uki_file = Some(path);
762+
break;
763+
}
764+
765+
// Check for traditional kernel in subdirectories
766+
if path.is_dir() {
767+
let vmlinuz = path.join("vmlinuz");
768+
let initramfs = path.join("initramfs.img");
751769
if vmlinuz.exists() && initramfs.exists() {
752770
debug!("Found kernel at: {:?}", vmlinuz);
753771
vmlinuz_path = Some(vmlinuz);
754772
initramfs_path = Some(initramfs);
755-
break;
756773
}
757774
}
758775
}
759776

760-
let vmlinuz_path = vmlinuz_path
761-
.ok_or_else(|| eyre!("No kernel found in /run/source-image/usr/lib/modules"))?;
762-
let initramfs_path = initramfs_path
763-
.ok_or_else(|| eyre!("No initramfs found in /run/source-image/usr/lib/modules"))?;
764-
765-
// Verify KVM access
766-
if !Utf8Path::new("/dev/kvm").exists() || !fs::File::open("/dev/kvm").is_ok() {
767-
return Err(eyre!("KVM device not accessible"));
768-
}
769-
770-
// Create QEMU mount points
771-
fs::create_dir_all("/run/qemu")?;
772777
let kernel_mount = "/run/qemu/kernel";
773778
let initramfs_mount = "/run/qemu/initramfs";
774-
fs::File::create(&kernel_mount)?;
775-
fs::File::create(&initramfs_mount)?;
776-
777-
// Bind mount kernel and initramfs
778-
let mut mount_cmd = Command::new("mount");
779-
mount_cmd.args([
780-
"--bind",
781-
"-o",
782-
"ro",
783-
vmlinuz_path.to_str().unwrap(),
784-
&kernel_mount,
785-
]);
786-
let status = mount_cmd.status().context("Failed to bind mount kernel")?;
787-
if !status.success() {
788-
return Err(eyre!("Failed to bind mount kernel"));
789-
}
790779

791-
let mut mount_cmd = Command::new("mount");
792-
mount_cmd.args([
793-
"--bind",
794-
"-o",
795-
"ro",
796-
initramfs_path.to_str().unwrap(),
797-
&initramfs_mount,
798-
]);
799-
let status = mount_cmd
800-
.status()
801-
.context("Failed to bind mount initramfs")?;
802-
if !status.success() {
803-
return Err(eyre!("Failed to bind mount initramfs"));
780+
// Extract from UKI if found, otherwise use traditional kernel
781+
if let Some(uki_path) = uki_file {
782+
debug!("Extracting kernel and initramfs from UKI: {:?}", uki_path);
783+
784+
// Extract .linux section (kernel) from UKI
785+
Command::new("objcopy")
786+
.args([
787+
"--dump-section",
788+
&format!(".linux={}", kernel_mount),
789+
uki_path.to_str().unwrap(),
790+
])
791+
.run()
792+
.map_err(|e| eyre!("Failed to extract kernel from UKI: {e}"))?;
793+
debug!("Extracted kernel from UKI to {}", kernel_mount);
794+
795+
// Extract .initrd section (initramfs) from UKI
796+
Command::new("objcopy")
797+
.args([
798+
"--dump-section",
799+
&format!(".initrd={}", initramfs_mount),
800+
uki_path.to_str().unwrap(),
801+
])
802+
.run()
803+
.map_err(|e| eyre!("Failed to extract initramfs from UKI: {e}"))?;
804+
debug!("Extracted initramfs from UKI to {}", initramfs_mount);
805+
} else {
806+
let vmlinuz_path = vmlinuz_path
807+
.ok_or_else(|| eyre!("No kernel found in /run/source-image/usr/lib/modules"))?;
808+
let initramfs_path = initramfs_path
809+
.ok_or_else(|| eyre!("No initramfs found in /run/source-image/usr/lib/modules"))?;
810+
811+
fs::File::create(&kernel_mount)?;
812+
fs::File::create(&initramfs_mount)?;
813+
814+
// Bind mount kernel and initramfs
815+
Command::new("mount")
816+
.args([
817+
"--bind",
818+
"-o",
819+
"ro",
820+
vmlinuz_path.to_str().unwrap(),
821+
&kernel_mount,
822+
])
823+
.run()
824+
.context("Failed to bind mount kernel")?;
825+
826+
Command::new("mount")
827+
.args([
828+
"--bind",
829+
"-o",
830+
"ro",
831+
initramfs_path.to_str().unwrap(),
832+
&initramfs_mount,
833+
])
834+
.run()
835+
.context("Failed to bind mount initramfs")?;
804836
}
805837

806838
// Process host mounts and prepare virtiofsd instances for each using async manager
@@ -993,7 +1025,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
9931025

9941026
std::fs::create_dir_all(CONTAINER_STATEDIR)?;
9951027

996-
// Configure qemu
1028+
// Configure qemu for direct kernel boot
1029+
debug!("Configuring QEMU for direct kernel boot");
9971030
let mut qemu_config = crate::qemu::QemuConfig::new_direct_boot(
9981031
opts.common.memory_mb()?,
9991032
opts.common.vcpus()?,
@@ -1014,7 +1047,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
10141047
let credential = crate::credentials::smbios_cred_for_root_ssh(&pubkey)?;
10151048
qemu_config.add_smbios_credential(credential);
10161049
}
1017-
// Build kernel command line
1050+
1051+
// Build kernel command line for direct boot
10181052
let mut kernel_cmdline = [
10191053
// At the core we boot from the mounted container's root,
10201054
"rootfstype=virtiofs",
@@ -1044,6 +1078,7 @@ StandardOutput=file:/dev/virtio-ports/executestatus
10441078
}
10451079

10461080
kernel_cmdline.extend(opts.kernel_args.clone());
1081+
qemu_config.set_kernel_cmdline(kernel_cmdline);
10471082

10481083
// TODO allocate unlinked unnamed file and pass via fd
10491084
let mut tmp_swapfile = None;
@@ -1151,9 +1186,7 @@ Options=
11511186
}
11521187
}
11531188

1154-
qemu_config
1155-
.set_kernel_cmdline(kernel_cmdline)
1156-
.set_console(opts.common.console);
1189+
qemu_config.set_console(opts.common.console);
11571190

11581191
// Add virtio-serial device for journal streaming
11591192
qemu_config.add_virtio_serial_out("org.bcvk.journal", "/run/journal.log".to_string(), false);

0 commit comments

Comments
 (0)