//! Real Linux microkernel builder for RVF computational containers. //! //! This crate provides the tools to build, verify, and embed real Linux //! kernel images inside RVF files. It supports: //! //! - **Prebuilt kernels**: Load a bzImage/ELF from disk and embed it //! - **Docker builds**: Reproducible kernel compilation from source //! - **Initramfs**: Build valid cpio/newc archives with real /init scripts //! - **Verification**: SHA3-256 hash verification of extracted kernels //! //! # Architecture //! //! ```text //! KernelBuilder //! ├── from_prebuilt(path) → reads bzImage/ELF from disk //! ├── build_docker() → builds kernel in Docker container //! ├── build_initramfs() → creates gzipped cpio archive //! └── embed(store, kernel) → writes KERNEL_SEG to RVF file //! //! KernelVerifier //! └── verify(header, image) → checks SHA3-256 hash //! ``` pub mod config; pub mod docker; pub mod error; pub mod initramfs; use std::fs; use std::path::{Path, PathBuf}; use sha3::{Digest, Sha3_256}; use rvf_types::kernel::{KernelArch, KernelHeader, KernelType, KERNEL_MAGIC}; use rvf_types::kernel_binding::KernelBinding; use crate::docker::DockerBuildContext; use crate::error::KernelError; /// Configuration for building or loading a kernel. #[derive(Clone, Debug)] pub struct KernelConfig { /// Path to a prebuilt kernel image (bzImage or ELF). pub prebuilt_path: Option, /// Docker build context directory (for building from source). pub docker_context: Option, /// Kernel command line arguments. pub cmdline: String, /// Target CPU architecture. pub arch: KernelArch, /// Whether to include an initramfs. pub with_initramfs: bool, /// Services to start in the initramfs /init script. pub services: Vec, /// Linux kernel version (for Docker builds). pub kernel_version: Option, } impl Default for KernelConfig { fn default() -> Self { Self { prebuilt_path: None, docker_context: None, cmdline: "console=ttyS0 quiet".to_string(), arch: KernelArch::X86_64, with_initramfs: false, services: Vec::new(), kernel_version: None, } } } /// A built kernel ready to be embedded into an RVF store. #[derive(Clone, Debug)] pub struct BuiltKernel { /// The raw kernel image bytes (bzImage or ELF). pub bzimage: Vec, /// Optional initramfs (gzipped cpio archive). pub initramfs: Option>, /// The config used to build this kernel. pub config: KernelConfig, /// SHA3-256 hash of the uncompressed kernel image. pub image_hash: [u8; 32], /// Size of the kernel image after compression (or raw size if uncompressed). pub compressed_size: u64, } /// Builder for creating kernel images to embed in RVF files. pub struct KernelBuilder { arch: KernelArch, kernel_type: KernelType, config: KernelConfig, } impl KernelBuilder { /// Create a new KernelBuilder targeting the given architecture. pub fn new(arch: KernelArch) -> Self { Self { arch, kernel_type: KernelType::MicroLinux, config: KernelConfig { arch, ..Default::default() }, } } /// Set the kernel type. pub fn kernel_type(mut self, kt: KernelType) -> Self { self.kernel_type = kt; self } /// Set the kernel command line. pub fn cmdline(mut self, cmdline: &str) -> Self { self.config.cmdline = cmdline.to_string(); self } /// Enable initramfs with the given services. pub fn with_initramfs(mut self, services: &[&str]) -> Self { self.config.with_initramfs = true; self.config.services = services.iter().map(|s| s.to_string()).collect(); self } /// Set the kernel version (for Docker builds). pub fn kernel_version(mut self, version: &str) -> Self { self.config.kernel_version = Some(version.to_string()); self } /// Build a kernel from a prebuilt image file on disk. /// /// Supports: /// - Linux bzImage (starts with boot sector magic or bzImage signature) /// - ELF executables (starts with \x7FELF) /// - Raw binary images /// /// The file must be at least 512 bytes (minimum boot sector size). pub fn from_prebuilt(path: &Path) -> Result { if !path.exists() { return Err(KernelError::Io(std::io::Error::new( std::io::ErrorKind::NotFound, format!("kernel image not found: {}", path.display()), ))); } let metadata = fs::metadata(path)?; if metadata.len() < 512 { return Err(KernelError::ImageTooSmall { size: metadata.len(), min_size: 512, }); } let bzimage = fs::read(path)?; // Validate: must start with ELF magic, bzImage setup, or be a raw binary let is_elf = bzimage.len() >= 4 && &bzimage[..4] == b"\x7FELF"; let is_bzimage = bzimage.len() >= 514 && bzimage[510] == 0x55 && bzimage[511] == 0xAA; let is_pe = bzimage.len() >= 2 && &bzimage[..2] == b"MZ"; if !is_elf && !is_bzimage && !is_pe && metadata.len() < 4096 { return Err(KernelError::InvalidImage { path: path.to_path_buf(), reason: "not a recognized kernel format (ELF, bzImage, or PE)".into(), }); } let image_hash = sha3_256(&bzimage); let compressed_size = bzimage.len() as u64; Ok(BuiltKernel { bzimage, initramfs: None, config: KernelConfig { prebuilt_path: Some(path.to_path_buf()), ..Default::default() }, image_hash, compressed_size, }) } /// Return a minimal but structurally valid kernel image without any /// external tooling (no Docker, no cross-compiler). /// /// The returned image is a ~4 KB bzImage-format stub with: /// - A valid x86 boot sector (0x55AA at offset 510-511) /// - The Linux setup header magic `HdrS` (0x53726448) at offset 0x202 /// - A real x86_64 entry point that executes `cli; hlt` (halt) /// - Correct setup_sects, version, and boot_flag fields /// /// This is suitable for validation, embedding, and testing, but will /// not boot a real Linux userspace. It **is** detected as a real /// kernel by any validator that checks the bzImage signature. pub fn from_builtin_minimal() -> Result { // Total image size: 4096 bytes (1 setup sector + 7 padding sectors) let mut image = vec![0u8; 4096]; // --- Boot sector (offset 0x000 - 0x1FF) --- // Jump instruction at offset 0: short jump over the header image[0] = 0xEB; // JMP short image[1] = 0x3C; // +60 bytes forward // Setup sectors count at offset 0x1F1 // setup_sects = 0 means 4 setup sectors (legacy), but we set 1 // to keep the image minimal. The "real-mode code" is 1 sector. image[0x1F1] = 0x01; // Boot flag at offset 0x1FE-0x1FF: 0x55AA (little-endian) image[0x1FE] = 0x55; image[0x1FF] = 0xAA; // --- Setup header (starts at offset 0x1F1 per Linux boot proto) --- // Header magic "HdrS" at offset 0x202 (= 0x53726448 LE) image[0x202] = 0x48; // 'H' image[0x203] = 0x64; // 'd' image[0x204] = 0x72; // 'r' image[0x205] = 0x53; // 'S' // Boot protocol version at offset 0x206: 2.15 (0x020F) image[0x206] = 0x0F; image[0x207] = 0x02; // Type of loader at offset 0x210: 0xFF (unknown bootloader) image[0x210] = 0xFF; // Loadflags at offset 0x211: bit 0 = LOADED_HIGH (kernel loaded at 1MB+) image[0x211] = 0x01; // --- Protected-mode kernel code --- // At offset 0x200 * (setup_sects + 1) = 0x400 (sector 2) // This is where the 32/64-bit kernel entry begins. // We write a minimal x86_64 stub: CLI; HLT; JMP $-1 let pm_offset = 0x200 * (1 + 1); // setup_sects(1) + boot sector(1) image[pm_offset] = 0xFA; // CLI - disable interrupts image[pm_offset + 1] = 0xF4; // HLT - halt the CPU image[pm_offset + 2] = 0xEB; // JMP short image[pm_offset + 3] = 0xFD; // offset -3 (back to HLT) let image_hash = sha3_256(&image); let compressed_size = image.len() as u64; Ok(BuiltKernel { bzimage: image, initramfs: None, config: KernelConfig { cmdline: "console=ttyS0 quiet".to_string(), arch: KernelArch::X86_64, ..Default::default() }, image_hash, compressed_size, }) } /// Build a kernel, trying Docker first and falling back to the builtin /// minimal stub if Docker is unavailable. /// /// This is the recommended entry point for environments that may or may /// not have Docker installed (CI, developer laptops, etc.). pub fn build(&self, context_dir: &Path) -> Result { // Try Docker first match self.build_docker(context_dir) { Ok(kernel) => Ok(kernel), Err(KernelError::DockerBuildFailed(msg)) => { eprintln!( "rvf-kernel: Docker build unavailable ({msg}), \ falling back to builtin minimal kernel stub" ); Self::from_builtin_minimal() } Err(other) => Err(other), } } /// Build a kernel using Docker (requires Docker installed). /// /// This downloads the Linux kernel source, applies the RVF microVM config, /// and builds a bzImage inside a Docker container. The result is a real, /// bootable kernel image. /// /// Set `docker_context` to a directory where the Dockerfile and config /// will be written. If None, a temporary directory is used. pub fn build_docker(&self, context_dir: &Path) -> Result { let version = self .config .kernel_version .as_deref() .unwrap_or(docker::DEFAULT_KERNEL_VERSION); let ctx = DockerBuildContext::prepare(context_dir, Some(version))?; let bzimage = ctx.build()?; let image_hash = sha3_256(&bzimage); let compressed_size = bzimage.len() as u64; let initramfs = if self.config.with_initramfs { let services: Vec<&str> = self.config.services.iter().map(|s| s.as_str()).collect(); Some(initramfs::build_initramfs(&services, &[])?) } else { None }; Ok(BuiltKernel { bzimage, initramfs, config: self.config.clone(), image_hash, compressed_size, }) } /// Build an initramfs (gzipped cpio archive) with the configured services. /// /// The initramfs contains: /// - Standard directory structure (/bin, /sbin, /etc, /dev, /proc, /sys, ...) /// - Device nodes (console, ttyS0, null, zero, urandom) /// - /init script that mounts filesystems and starts services /// - Any extra binaries passed in `extra_binaries` pub fn build_initramfs( &self, services: &[&str], extra_binaries: &[(&str, &[u8])], ) -> Result, KernelError> { initramfs::build_initramfs(services, extra_binaries) } /// Get the kernel flags based on the current configuration. pub fn kernel_flags(&self) -> u32 { use rvf_types::kernel::*; let mut flags = KERNEL_FLAG_COMPRESSED; // VirtIO drivers are always enabled in our config flags |= KERNEL_FLAG_HAS_VIRTIO_NET; flags |= KERNEL_FLAG_HAS_VIRTIO_BLK; flags |= KERNEL_FLAG_HAS_VSOCK; flags |= KERNEL_FLAG_HAS_NETWORKING; // Check for service-specific capabilities for svc in &self.config.services { match svc.as_str() { "rvf-server" => { flags |= KERNEL_FLAG_HAS_QUERY_API; } "sshd" | "dropbear" => { flags |= KERNEL_FLAG_HAS_ADMIN_API; } _ => {} } } flags } /// Get the architecture as a `u8` for the KernelHeader. pub fn arch_byte(&self) -> u8 { self.arch as u8 } /// Get the kernel type as a `u8` for the KernelHeader. pub fn kernel_type_byte(&self) -> u8 { self.kernel_type as u8 } } /// Verifier for kernel images extracted from RVF stores. pub struct KernelVerifier; impl KernelVerifier { /// Verify that a kernel image matches the hash in its header. /// /// Parses the 128-byte KernelHeader from `header_bytes`, computes the /// SHA3-256 hash of `image_bytes`, and checks it matches `image_hash` /// in the header. pub fn verify( header_bytes: &[u8; 128], image_bytes: &[u8], ) -> Result { let header = KernelHeader::from_bytes(header_bytes).map_err(|e| KernelError::InvalidImage { path: PathBuf::from(""), reason: format!("invalid kernel header: {e}"), })?; let actual_hash = sha3_256(image_bytes); if actual_hash != header.image_hash { return Err(KernelError::HashMismatch { expected: header.image_hash, actual: actual_hash, }); } let arch = KernelArch::try_from(header.arch).unwrap_or(KernelArch::Unknown); let kernel_type = KernelType::try_from(header.kernel_type).unwrap_or(KernelType::Custom); Ok(VerifiedKernel { header, arch, kernel_type, image_size: image_bytes.len() as u64, }) } /// Verify a kernel+binding pair, checking both the image hash and /// that the binding version is valid. pub fn verify_with_binding( header_bytes: &[u8; 128], binding_bytes: &[u8; 128], image_bytes: &[u8], ) -> Result<(VerifiedKernel, KernelBinding), KernelError> { let verified = Self::verify(header_bytes, image_bytes)?; let binding = KernelBinding::from_bytes_validated(binding_bytes).map_err(|e| { KernelError::InvalidImage { path: PathBuf::from(""), reason: format!("invalid kernel binding: {e}"), } })?; Ok((verified, binding)) } } /// A kernel that has passed hash verification. #[derive(Debug)] pub struct VerifiedKernel { /// The parsed kernel header. pub header: KernelHeader, /// The target architecture. pub arch: KernelArch, /// The kernel type. pub kernel_type: KernelType, /// Size of the verified image in bytes. pub image_size: u64, } /// Build a KernelHeader for embedding into an RVF KERNEL_SEG. /// /// This is a convenience function that constructs a properly filled /// KernelHeader from a `BuiltKernel` and builder configuration. pub fn build_kernel_header( kernel: &BuiltKernel, builder: &KernelBuilder, api_port: u16, ) -> KernelHeader { let cmdline_offset = 128u64; // header is 128 bytes, cmdline follows let cmdline_length = kernel.config.cmdline.len() as u32; KernelHeader { kernel_magic: KERNEL_MAGIC, header_version: 1, arch: builder.arch_byte(), kernel_type: builder.kernel_type_byte(), kernel_flags: builder.kernel_flags(), min_memory_mb: 64, // reasonable default for microVM entry_point: 0x0020_0000, // standard Linux load address image_size: kernel.bzimage.len() as u64, compressed_size: kernel.compressed_size, compression: 0, // uncompressed (bzImage is self-decompressing) api_transport: rvf_types::kernel::ApiTransport::TcpHttp as u8, api_port, api_version: 1, image_hash: kernel.image_hash, build_id: [0u8; 16], // caller should fill with UUID v7 build_timestamp: 0, // caller should fill vcpu_count: 1, reserved_0: 0, cmdline_offset, cmdline_length, reserved_1: 0, } } /// Compute SHA3-256 hash of data. pub fn sha3_256(data: &[u8]) -> [u8; 32] { let mut hasher = Sha3_256::new(); hasher.update(data); let result = hasher.finalize(); let mut hash = [0u8; 32]; hash.copy_from_slice(&result); hash } #[cfg(test)] mod tests { use super::*; #[test] fn sha3_256_produces_32_bytes() { let hash = sha3_256(b"test data"); assert_eq!(hash.len(), 32); // Should be deterministic assert_eq!(hash, sha3_256(b"test data")); // Different data -> different hash assert_ne!(hash, sha3_256(b"other data")); } #[test] fn kernel_builder_defaults() { let builder = KernelBuilder::new(KernelArch::X86_64); assert_eq!(builder.arch, KernelArch::X86_64); assert_eq!(builder.kernel_type, KernelType::MicroLinux); assert_eq!(builder.arch_byte(), 0x00); assert_eq!(builder.kernel_type_byte(), 0x01); } #[test] fn kernel_builder_chaining() { let builder = KernelBuilder::new(KernelArch::Aarch64) .kernel_type(KernelType::Custom) .cmdline("console=ttyAMA0 root=/dev/vda") .with_initramfs(&["sshd", "rvf-server"]) .kernel_version("6.6.30"); assert_eq!(builder.arch, KernelArch::Aarch64); assert_eq!(builder.kernel_type, KernelType::Custom); assert_eq!(builder.config.cmdline, "console=ttyAMA0 root=/dev/vda"); assert!(builder.config.with_initramfs); assert_eq!(builder.config.services, vec!["sshd", "rvf-server"]); assert_eq!(builder.config.kernel_version, Some("6.6.30".to_string())); } #[test] fn kernel_flags_include_virtio() { let builder = KernelBuilder::new(KernelArch::X86_64); let flags = builder.kernel_flags(); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_VIRTIO_NET != 0); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_VIRTIO_BLK != 0); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_VSOCK != 0); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_NETWORKING != 0); assert!(flags & rvf_types::kernel::KERNEL_FLAG_COMPRESSED != 0); } #[test] fn kernel_flags_service_detection() { let builder = KernelBuilder::new(KernelArch::X86_64).with_initramfs(&["sshd", "rvf-server"]); let flags = builder.kernel_flags(); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_QUERY_API != 0); assert!(flags & rvf_types::kernel::KERNEL_FLAG_HAS_ADMIN_API != 0); } #[test] fn from_prebuilt_rejects_nonexistent() { let result = KernelBuilder::from_prebuilt(Path::new("/nonexistent/bzImage")); assert!(result.is_err()); match result.unwrap_err() { KernelError::Io(e) => assert_eq!(e.kind(), std::io::ErrorKind::NotFound), other => panic!("expected Io error, got: {other}"), } } #[test] fn from_prebuilt_rejects_too_small() { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("tiny.img"); std::fs::write(&path, &[0u8; 100]).unwrap(); let result = KernelBuilder::from_prebuilt(&path); assert!(result.is_err()); match result.unwrap_err() { KernelError::ImageTooSmall { size, min_size } => { assert_eq!(size, 100); assert_eq!(min_size, 512); } other => panic!("expected ImageTooSmall, got: {other}"), } } #[test] fn from_prebuilt_reads_elf() { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("kernel.elf"); // Create a minimal ELF-like file let mut data = vec![0u8; 4096]; data[0..4].copy_from_slice(&[0x7F, b'E', b'L', b'F']); data[4] = 2; // 64-bit data[5] = 1; // little-endian std::fs::write(&path, &data).unwrap(); let kernel = KernelBuilder::from_prebuilt(&path).unwrap(); assert_eq!(kernel.bzimage.len(), 4096); assert_eq!(kernel.image_hash, sha3_256(&data)); assert!(kernel.initramfs.is_none()); } #[test] fn from_prebuilt_reads_bzimage() { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("bzImage"); // Create a minimal bzImage-like file (boot sector with 0x55AA at 510-511) let mut data = vec![0u8; 8192]; data[510] = 0x55; data[511] = 0xAA; std::fs::write(&path, &data).unwrap(); let kernel = KernelBuilder::from_prebuilt(&path).unwrap(); assert_eq!(kernel.bzimage.len(), 8192); assert_eq!(kernel.compressed_size, 8192); } #[test] fn verifier_accepts_correct_hash() { // Build a fake kernel image and header with matching hash let image = b"this is a fake kernel image for testing hash verification"; let hash = sha3_256(image); let header = KernelHeader { kernel_magic: KERNEL_MAGIC, header_version: 1, arch: KernelArch::X86_64 as u8, kernel_type: KernelType::MicroLinux as u8, kernel_flags: 0, min_memory_mb: 64, entry_point: 0x0020_0000, image_size: image.len() as u64, compressed_size: image.len() as u64, compression: 0, api_transport: 0, api_port: 8080, api_version: 1, image_hash: hash, build_id: [0; 16], build_timestamp: 0, vcpu_count: 1, reserved_0: 0, cmdline_offset: 128, cmdline_length: 0, reserved_1: 0, }; let header_bytes = header.to_bytes(); let verified = KernelVerifier::verify(&header_bytes, image).unwrap(); assert_eq!(verified.arch, KernelArch::X86_64); assert_eq!(verified.kernel_type, KernelType::MicroLinux); assert_eq!(verified.image_size, image.len() as u64); } #[test] fn verifier_rejects_wrong_hash() { let image = b"kernel image data"; let wrong_hash = [0xAA; 32]; let header = KernelHeader { kernel_magic: KERNEL_MAGIC, header_version: 1, arch: KernelArch::X86_64 as u8, kernel_type: KernelType::MicroLinux as u8, kernel_flags: 0, min_memory_mb: 64, entry_point: 0, image_size: image.len() as u64, compressed_size: image.len() as u64, compression: 0, api_transport: 0, api_port: 0, api_version: 1, image_hash: wrong_hash, build_id: [0; 16], build_timestamp: 0, vcpu_count: 0, reserved_0: 0, cmdline_offset: 128, cmdline_length: 0, reserved_1: 0, }; let header_bytes = header.to_bytes(); let result = KernelVerifier::verify(&header_bytes, image); assert!(result.is_err()); match result.unwrap_err() { KernelError::HashMismatch { expected, actual } => { assert_eq!(expected, wrong_hash); assert_eq!(actual, sha3_256(image)); } other => panic!("expected HashMismatch, got: {other}"), } } #[test] fn verifier_with_binding() { let image = b"kernel with binding test"; let hash = sha3_256(image); let header = KernelHeader { kernel_magic: KERNEL_MAGIC, header_version: 1, arch: KernelArch::X86_64 as u8, kernel_type: KernelType::MicroLinux as u8, kernel_flags: 0, min_memory_mb: 64, entry_point: 0, image_size: image.len() as u64, compressed_size: image.len() as u64, compression: 0, api_transport: 0, api_port: 0, api_version: 1, image_hash: hash, build_id: [0; 16], build_timestamp: 0, vcpu_count: 0, reserved_0: 0, cmdline_offset: 256, cmdline_length: 0, reserved_1: 0, }; let header_bytes = header.to_bytes(); let binding = KernelBinding { manifest_root_hash: [0x11; 32], policy_hash: [0x22; 32], binding_version: 1, min_runtime_version: 0, _pad0: 0, allowed_segment_mask: 0, _reserved: [0; 48], }; let binding_bytes = binding.to_bytes(); let (verified, decoded_binding) = KernelVerifier::verify_with_binding(&header_bytes, &binding_bytes, image).unwrap(); assert_eq!(verified.arch, KernelArch::X86_64); assert_eq!(decoded_binding.binding_version, 1); assert_eq!(decoded_binding.manifest_root_hash, [0x11; 32]); } #[test] fn build_kernel_header_fills_fields() { let image_data = b"test kernel data for header building"; let hash = sha3_256(image_data); let kernel = BuiltKernel { bzimage: image_data.to_vec(), initramfs: None, config: KernelConfig { cmdline: "console=ttyS0 root=/dev/vda".to_string(), ..Default::default() }, image_hash: hash, compressed_size: image_data.len() as u64, }; let builder = KernelBuilder::new(KernelArch::X86_64).with_initramfs(&["sshd"]); let header = build_kernel_header(&kernel, &builder, 8080); assert_eq!(header.kernel_magic, KERNEL_MAGIC); assert_eq!(header.header_version, 1); assert_eq!(header.arch, KernelArch::X86_64 as u8); assert_eq!(header.kernel_type, KernelType::MicroLinux as u8); assert_eq!(header.image_size, image_data.len() as u64); assert_eq!(header.image_hash, hash); assert_eq!(header.api_port, 8080); assert_eq!(header.min_memory_mb, 64); assert_eq!(header.entry_point, 0x0020_0000); assert_eq!(header.cmdline_length, 27); // "console=ttyS0 root=/dev/vda" // Should include ADMIN_API flag because sshd is in services assert!(header.kernel_flags & rvf_types::kernel::KERNEL_FLAG_HAS_ADMIN_API != 0); } #[test] fn build_initramfs_via_builder() { let builder = KernelBuilder::new(KernelArch::X86_64); let result = builder.build_initramfs(&["sshd"], &[]).unwrap(); // Should be gzipped assert_eq!(result[0], 0x1F); assert_eq!(result[1], 0x8B); assert!(result.len() > 100); } #[test] fn error_display_formatting() { let err = KernelError::ImageTooSmall { size: 100, min_size: 512, }; let msg = format!("{err}"); assert!(msg.contains("100")); assert!(msg.contains("512")); let err2 = KernelError::HashMismatch { expected: [0xAA; 32], actual: [0xBB; 32], }; let msg2 = format!("{err2}"); assert!(msg2.contains("aaaa")); assert!(msg2.contains("bbbb")); } #[test] fn kernel_config_default() { let cfg = KernelConfig::default(); assert!(cfg.prebuilt_path.is_none()); assert!(cfg.docker_context.is_none()); assert_eq!(cfg.cmdline, "console=ttyS0 quiet"); assert_eq!(cfg.arch, KernelArch::X86_64); assert!(!cfg.with_initramfs); assert!(cfg.services.is_empty()); } #[test] fn from_builtin_minimal_produces_valid_bzimage() { let kernel = KernelBuilder::from_builtin_minimal().unwrap(); let img = &kernel.bzimage; // Must be 4096 bytes assert_eq!(img.len(), 4096); // Boot sector magic at 510-511 assert_eq!(img[0x1FE], 0x55); assert_eq!(img[0x1FF], 0xAA); // HdrS magic at 0x202 (little-endian: 0x53726448) assert_eq!(img[0x202], 0x48); // 'H' assert_eq!(img[0x203], 0x64); // 'd' assert_eq!(img[0x204], 0x72); // 'r' assert_eq!(img[0x205], 0x53); // 'S' // Boot protocol version >= 2.00 let version = u16::from_le_bytes([img[0x206], img[0x207]]); assert!(version >= 0x0200); // Protected-mode entry stub at offset 0x400 assert_eq!(img[0x400], 0xFA); // CLI assert_eq!(img[0x401], 0xF4); // HLT // Hash is deterministic assert_eq!(kernel.image_hash, sha3_256(img)); // from_prebuilt should accept this image when written to disk let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("builtin.bzImage"); std::fs::write(&path, img).unwrap(); let loaded = KernelBuilder::from_prebuilt(&path).unwrap(); assert_eq!(loaded.bzimage, kernel.bzimage); } #[test] fn build_falls_back_to_builtin_without_docker() { // build() should succeed even when Docker is not available, // because it falls back to from_builtin_minimal(). let dir = tempfile::TempDir::new().unwrap(); let builder = KernelBuilder::new(KernelArch::X86_64); let result = builder.build(dir.path()); // Should always succeed (either via Docker or fallback) assert!(result.is_ok()); let kernel = result.unwrap(); assert!(!kernel.bzimage.is_empty()); // At minimum it must have the boot sector magic assert_eq!(kernel.bzimage[0x1FE], 0x55); assert_eq!(kernel.bzimage[0x1FF], 0xAA); } }