Skip to content

Commit 2c3c056

Browse files
authored
ephemeral: Support extracting UKIs (#144)
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. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent 6e3788f commit 2c3c056

File tree

2 files changed

+83
-53
lines changed

2 files changed

+83
-53
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: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -737,17 +737,36 @@ 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");
742-
let mut vmlinuz_path = None;
743-
let mut initramfs_path = None;
750+
let mut uki_file: Option<Utf8PathBuf> = None;
751+
let mut vmlinuz_path: Option<Utf8PathBuf> = None;
752+
let mut initramfs_path: Option<Utf8PathBuf> = 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 = Utf8PathBuf::from_path_buf(entry.path())
757+
.map_err(|p| eyre!("Path is not valid UTF-8: {}", p.display()))?;
758+
759+
// Check for UKI (.efi file)
760+
if path.is_file() && path.extension() == Some("efi") {
761+
debug!("Found UKI file: {:?}", path);
762+
uki_file = Some(path);
763+
break;
764+
}
765+
766+
// Check for traditional kernel in subdirectories
767+
if path.is_dir() {
768+
let vmlinuz = path.join("vmlinuz");
769+
let initramfs = path.join("initramfs.img");
751770
if vmlinuz.exists() && initramfs.exists() {
752771
debug!("Found kernel at: {:?}", vmlinuz);
753772
vmlinuz_path = Some(vmlinuz);
@@ -757,50 +776,59 @@ pub(crate) async fn run_impl(opts: RunEphemeralOpts) -> Result<()> {
757776
}
758777
}
759778

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")?;
772779
let kernel_mount = "/run/qemu/kernel";
773780
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-
}
790781

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

806834
// Process host mounts and prepare virtiofsd instances for each using async manager
@@ -995,7 +1023,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
9951023

9961024
std::fs::create_dir_all(CONTAINER_STATEDIR)?;
9971025

998-
// Configure qemu
1026+
// Configure qemu for direct kernel boot
1027+
debug!("Configuring QEMU for direct kernel boot");
9991028
let mut qemu_config = crate::qemu::QemuConfig::new_direct_boot(
10001029
opts.common.memory_mb()?,
10011030
opts.common.vcpus()?,
@@ -1016,7 +1045,8 @@ StandardOutput=file:/dev/virtio-ports/executestatus
10161045
let credential = crate::credentials::smbios_cred_for_root_ssh(&pubkey)?;
10171046
qemu_config.add_smbios_credential(credential);
10181047
}
1019-
// Build kernel command line
1048+
1049+
// Build kernel command line for direct boot
10201050
let mut kernel_cmdline = [
10211051
// At the core we boot from the mounted container's root,
10221052
"rootfstype=virtiofs",
@@ -1046,6 +1076,7 @@ StandardOutput=file:/dev/virtio-ports/executestatus
10461076
}
10471077

10481078
kernel_cmdline.extend(opts.kernel_args.clone());
1079+
qemu_config.set_kernel_cmdline(kernel_cmdline);
10491080

10501081
// TODO allocate unlinked unnamed file and pass via fd
10511082
let mut tmp_swapfile = None;
@@ -1153,9 +1184,7 @@ Options=
11531184
}
11541185
}
11551186

1156-
qemu_config
1157-
.set_kernel_cmdline(kernel_cmdline)
1158-
.set_console(opts.common.console);
1187+
qemu_config.set_console(opts.common.console);
11591188

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

0 commit comments

Comments
 (0)