Files
wifi-densepose/vendor/ruvector/crates/rvf/rvf-kernel/src/lib.rs

862 lines
30 KiB
Rust

//! 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<PathBuf>,
/// Docker build context directory (for building from source).
pub docker_context: Option<PathBuf>,
/// 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<String>,
/// Linux kernel version (for Docker builds).
pub kernel_version: Option<String>,
}
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<u8>,
/// Optional initramfs (gzipped cpio archive).
pub initramfs: Option<Vec<u8>>,
/// 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<BuiltKernel, KernelError> {
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<BuiltKernel, KernelError> {
// 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<BuiltKernel, KernelError> {
// 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<BuiltKernel, KernelError> {
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<Vec<u8>, 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<VerifiedKernel, KernelError> {
let header =
KernelHeader::from_bytes(header_bytes).map_err(|e| KernelError::InvalidImage {
path: PathBuf::from("<embedded>"),
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("<embedded>"),
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);
}
}