Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,683 @@
//! Real Linux kernel configuration for microVM boot.
//!
//! This module provides a minimal, valid Linux kernel `.config` suitable for
//! building a microVM kernel (Firecracker / QEMU microvm). The config enables
//! only what is needed: VirtIO drivers, networking, BPF, security hardening,
//! and a minimal filesystem. Everything else (sound, USB, DRM, wireless,
//! loadable modules) is disabled to keep the image small.
/// Minimal Linux kernel configuration for RVF microVM boot.
///
/// This is a valid Linux kernel `.config` file content. Key design decisions:
/// - No loadable modules (CONFIG_MODULES is not set) for security
/// - VirtIO PCI/block/net/vsock for Firecracker/QEMU compatibility
/// - BPF + JIT for eBPF programs embedded in RVF
/// - Security hardening (KASLR, stack protector, lockdown LSM)
/// - PREEMPT_NONE + NO_HZ_FULL for low-latency microVM
/// - Minimal filesystem support (ext4 + tmpfs + proc/sys/devtmpfs)
/// - No sound, USB, DRM, wireless, or other desktop hardware
pub const MICROVM_KERNEL_CONFIG: &str = r#"#
# RVF MicroVM Kernel Configuration
# Target: Linux 6.8.x for Firecracker / QEMU microvm
# Generated by rvf-kernel for RuVector Format computational containers
#
#
# General setup
#
CONFIG_LOCALVERSION="-rvf"
CONFIG_DEFAULT_HOSTNAME="rvf"
CONFIG_SWAP=y
CONFIG_SYSVIPC=y
CONFIG_POSIX_MQUEUE=y
CONFIG_AUDIT=y
CONFIG_NO_HZ_FULL=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT_NONE=y
CONFIG_TICK_CPU_ACCOUNTING=y
CONFIG_IKCONFIG=y
CONFIG_IKCONFIG_PROC=y
CONFIG_LOG_BUF_SHIFT=14
CONFIG_CGROUPS=y
CONFIG_CGROUP_SCHED=y
CONFIG_CGROUP_PIDS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_MEMCG=y
CONFIG_NAMESPACES=y
CONFIG_UTS_NS=y
CONFIG_IPC_NS=y
CONFIG_PID_NS=y
CONFIG_NET_NS=y
CONFIG_USER_NS=y
# CONFIG_MODULES is not set
CONFIG_CC_OPTIMIZE_FOR_SIZE=y
CONFIG_EXPERT=y
CONFIG_MULTIUSER=y
CONFIG_SYSFS_SYSCALL=y
CONFIG_FHANDLE=y
CONFIG_POSIX_TIMERS=y
CONFIG_PRINTK=y
CONFIG_BUG=y
CONFIG_ELF_CORE=y
CONFIG_BASE_FULL=y
CONFIG_FUTEX=y
CONFIG_EPOLL=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EVENTFD=y
CONFIG_AIO=y
CONFIG_IO_URING=y
CONFIG_ADVISE_SYSCALLS=y
CONFIG_KALLSYMS=y
CONFIG_EMBEDDED=y
#
# Processor type and features
#
CONFIG_64BIT=y
CONFIG_SMP=y
CONFIG_NR_CPUS=64
CONFIG_SCHED_SMT=y
CONFIG_X86_X2APIC=y
CONFIG_X86_LOCAL_APIC=y
CONFIG_X86_IO_APIC=y
CONFIG_X86_TSC=y
CONFIG_MICROCODE=y
CONFIG_X86_MSR=y
CONFIG_X86_CPUID=y
CONFIG_PARAVIRT=y
CONFIG_PARAVIRT_SPINLOCKS=y
CONFIG_KVM_GUEST=y
CONFIG_HYPERVISOR_GUEST=y
CONFIG_RANDOMIZE_BASE=y
CONFIG_X86_DIRECT_GBPAGES=y
CONFIG_NUMA=y
CONFIG_MTRR=y
CONFIG_X86_PAT=y
#
# Memory management
#
CONFIG_SPARSEMEM_VMEMMAP=y
CONFIG_MEMORY_HOTPLUG=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_COMPACTION=y
CONFIG_KSM=y
#
# Networking
#
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_INET=y
CONFIG_IP_MULTICAST=y
CONFIG_IP_ADVANCED_ROUTER=y
CONFIG_IP_ROUTE_MULTIPATH=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_TCP_CONG_CUBIC=y
CONFIG_TCP_CONG_BBR=y
CONFIG_DEFAULT_BBR=y
CONFIG_IPV6=y
CONFIG_NETFILTER=y
CONFIG_NF_CONNTRACK=y
CONFIG_NF_TABLES=y
CONFIG_NETFILTER_XT_MATCH_CONNTRACK=y
CONFIG_IP_NF_IPTABLES=y
CONFIG_IP_NF_FILTER=y
CONFIG_IP_NF_NAT=y
CONFIG_IP_NF_MANGLE=y
CONFIG_VSOCKETS=y
CONFIG_VIRTIO_VSOCKETS=y
CONFIG_BRIDGE=y
CONFIG_VLAN_8021Q=y
#
# Device drivers — VirtIO (Firecracker/QEMU)
#
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_CONSOLE=y
CONFIG_VIRTIO_MMIO=y
CONFIG_VIRTIO_INPUT=y
CONFIG_HW_RANDOM_VIRTIO=y
#
# Block devices
#
CONFIG_BLK_DEV=y
CONFIG_BLK_DEV_LOOP=y
CONFIG_BLK_DEV_RAM=y
CONFIG_BLK_DEV_RAM_SIZE=65536
#
# SCSI (for virtio-scsi)
#
CONFIG_SCSI=y
CONFIG_BLK_DEV_SD=y
CONFIG_SCSI_VIRTIO=y
#
# Serial / console
#
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_HW_RANDOM=y
CONFIG_TTY=y
CONFIG_VT=y
CONFIG_VT_CONSOLE=y
#
# Filesystems
#
CONFIG_EXT4_FS=y
CONFIG_EXT4_FS_POSIX_ACL=y
CONFIG_EXT4_FS_SECURITY=y
CONFIG_TMPFS=y
CONFIG_TMPFS_POSIX_ACL=y
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
CONFIG_SYSFS=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
# CONFIG_FUSE_FS is not set
# CONFIG_NFS_FS is not set
# CONFIG_CIFS is not set
#
# BPF subsystem
#
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
CONFIG_CGROUP_BPF=y
CONFIG_BPF_LSM=y
CONFIG_BPF_STREAM_PARSER=y
#
# Security
#
CONFIG_SECURITY=y
CONFIG_SECURITY_NETWORK=y
CONFIG_SECURITY_LOCKDOWN_LSM=y
CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y
CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y
CONFIG_SECURITY_YAMA=y
CONFIG_SECURITY_LANDLOCK=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
CONFIG_STACKPROTECTOR=y
CONFIG_STACKPROTECTOR_STRONG=y
CONFIG_FORTIFY_SOURCE=y
CONFIG_HARDENED_USERCOPY=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y
# CONFIG_SECURITY_SELINUX is not set
# CONFIG_SECURITY_APPARMOR is not set
#
# Crypto
#
CONFIG_CRYPTO=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_SHA512=y
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_GCM=y
CONFIG_CRYPTO_CHACHA20POLY1305=y
CONFIG_CRYPTO_ECDH=y
CONFIG_CRYPTO_CURVE25519=y
#
# Disabled subsystems (keep image small)
#
# CONFIG_SOUND is not set
# CONFIG_USB_SUPPORT is not set
# CONFIG_DRM is not set
# CONFIG_WIRELESS is not set
# CONFIG_WLAN is not set
# CONFIG_BLUETOOTH is not set
# CONFIG_INPUT_JOYSTICK is not set
# CONFIG_INPUT_TABLET is not set
# CONFIG_INPUT_TOUCHSCREEN is not set
# CONFIG_MEDIA_SUPPORT is not set
# CONFIG_AGP is not set
# CONFIG_PCMCIA is not set
# CONFIG_INFINIBAND is not set
# CONFIG_ISDN is not set
# CONFIG_PARPORT is not set
# CONFIG_PHONE is not set
# CONFIG_ACCESSIBILITY is not set
# CONFIG_FIRMWARE_EDID is not set
# CONFIG_LOGO is not set
# CONFIG_FB is not set
# CONFIG_BACKLIGHT_CLASS_DEVICE is not set
#
# Debugging (minimal for production)
#
CONFIG_PRINTK_TIME=y
CONFIG_MAGIC_SYSRQ=y
CONFIG_DEBUG_KERNEL=y
# CONFIG_DEBUG_INFO_DWARF5 is not set
# CONFIG_KPROBES is not set
# CONFIG_FTRACE is not set
"#;
/// Ultra-fast boot kernel configuration optimized for sub-100ms cold start.
///
/// Compared to the general-purpose `MICROVM_KERNEL_CONFIG`, this strips:
/// - NUMA detection, memory hotplug, THP, KSM, compaction
/// - cgroups, namespaces, audit, POSIX IPC
/// - SCSI subsystem, loop/RAM block devices, ext4
/// - Netfilter, bridge, VLAN, IPv6
/// - All debug/tracing infrastructure
/// - Reduced NR_CPUS (4 vs 64) for faster SMP init
/// - LZ4 compression for fastest decompression
/// - Optimized for performance (not size)
///
/// Trade-offs:
/// - No container isolation (no cgroups/namespaces)
/// - No persistent filesystem (initramfs-only boot)
/// - No IPv6 networking
/// - No firewall/NAT (no netfilter)
/// - Slightly larger image (performance-optimized codegen)
pub const ULTRAFAST_BOOT_CONFIG: &str = r#"#
# RVF Ultra-Fast Boot Kernel Configuration
# Target: Linux 6.8.x for sub-100ms cold start
# Optimized for: minimal init path, fastest decompression, direct-to-service
#
#
# General setup — stripped to bare minimum
#
CONFIG_LOCALVERSION="-rvf-fast"
CONFIG_DEFAULT_HOSTNAME="rvf"
# CONFIG_SWAP is not set
# CONFIG_SYSVIPC is not set
# CONFIG_POSIX_MQUEUE is not set
# CONFIG_AUDIT is not set
CONFIG_NO_HZ_FULL=y
CONFIG_HIGH_RES_TIMERS=y
CONFIG_PREEMPT_NONE=y
CONFIG_TICK_CPU_ACCOUNTING=y
# CONFIG_IKCONFIG is not set
# CONFIG_IKCONFIG_PROC is not set
CONFIG_LOG_BUF_SHIFT=12
# CONFIG_CGROUPS is not set
# CONFIG_NAMESPACES is not set
# CONFIG_MODULES is not set
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
CONFIG_EXPERT=y
CONFIG_MULTIUSER=y
# CONFIG_SYSFS_SYSCALL is not set
CONFIG_FHANDLE=y
CONFIG_POSIX_TIMERS=y
CONFIG_PRINTK=y
CONFIG_BUG=y
# CONFIG_ELF_CORE is not set
# CONFIG_BASE_FULL is not set
CONFIG_FUTEX=y
CONFIG_EPOLL=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EVENTFD=y
CONFIG_AIO=y
# CONFIG_IO_URING is not set
# CONFIG_ADVISE_SYSCALLS is not set
# CONFIG_KALLSYMS is not set
CONFIG_EMBEDDED=y
#
# Processor — minimal SMP, no NUMA
#
CONFIG_64BIT=y
CONFIG_SMP=y
CONFIG_NR_CPUS=4
# CONFIG_SCHED_SMT is not set
CONFIG_X86_LOCAL_APIC=y
CONFIG_X86_IO_APIC=y
CONFIG_X86_TSC=y
# CONFIG_MICROCODE is not set
# CONFIG_X86_MSR is not set
# CONFIG_X86_CPUID is not set
# CONFIG_PARAVIRT is not set
# CONFIG_KVM_GUEST is not set
CONFIG_HYPERVISOR_GUEST=y
CONFIG_RANDOMIZE_BASE=y
# CONFIG_NUMA is not set
# CONFIG_MTRR is not set
#
# Memory — no hotplug, no THP, no KSM
#
CONFIG_SPARSEMEM_VMEMMAP=y
# CONFIG_MEMORY_HOTPLUG is not set
# CONFIG_TRANSPARENT_HUGEPAGE is not set
# CONFIG_COMPACTION is not set
# CONFIG_KSM is not set
#
# Networking — minimal TCP/IP only
#
CONFIG_NET=y
CONFIG_PACKET=y
CONFIG_UNIX=y
CONFIG_INET=y
CONFIG_IP_PNP=y
CONFIG_IP_PNP_DHCP=y
CONFIG_TCP_CONG_CUBIC=y
# CONFIG_IPV6 is not set
# CONFIG_NETFILTER is not set
CONFIG_VSOCKETS=y
CONFIG_VIRTIO_VSOCKETS=y
# CONFIG_BRIDGE is not set
# CONFIG_VLAN_8021Q is not set
#
# Device drivers — VirtIO only
#
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO_MMIO=y
CONFIG_HW_RANDOM_VIRTIO=y
#
# Block — no loop, no RAM disk, no SCSI
#
CONFIG_BLK_DEV=y
# CONFIG_BLK_DEV_LOOP is not set
# CONFIG_BLK_DEV_RAM is not set
# CONFIG_SCSI is not set
#
# Serial / console — minimal
#
CONFIG_SERIAL_8250=y
CONFIG_SERIAL_8250_CONSOLE=y
CONFIG_HW_RANDOM=y
CONFIG_TTY=y
# CONFIG_VT is not set
#
# Filesystems — initramfs only, no persistent FS
#
CONFIG_TMPFS=y
CONFIG_PROC_FS=y
CONFIG_PROC_SYSCTL=y
CONFIG_SYSFS=y
CONFIG_DEVTMPFS=y
CONFIG_DEVTMPFS_MOUNT=y
# CONFIG_EXT4_FS is not set
# CONFIG_FUSE_FS is not set
# CONFIG_NFS_FS is not set
# CONFIG_CIFS is not set
#
# Initramfs compression — LZ4 for fastest decompression
#
CONFIG_RD_LZ4=y
CONFIG_INITRAMFS_COMPRESSION_LZ4=y
#
# BPF subsystem
#
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_BPF_JIT=y
CONFIG_BPF_JIT_ALWAYS_ON=y
CONFIG_BPF_UNPRIV_DEFAULT_OFF=y
#
# Security — essential hardening only
#
CONFIG_SECURITY=y
CONFIG_SECURITY_LOCKDOWN_LSM=y
CONFIG_SECURITY_LOCKDOWN_LSM_EARLY=y
CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y
CONFIG_SECCOMP=y
CONFIG_SECCOMP_FILTER=y
CONFIG_STACKPROTECTOR=y
CONFIG_STACKPROTECTOR_STRONG=y
CONFIG_FORTIFY_SOURCE=y
# CONFIG_SECURITY_SELINUX is not set
# CONFIG_SECURITY_APPARMOR is not set
# CONFIG_SECURITY_YAMA is not set
# CONFIG_SECURITY_LANDLOCK is not set
#
# Crypto — minimal
#
CONFIG_CRYPTO=y
CONFIG_CRYPTO_SHA256=y
CONFIG_CRYPTO_AES=y
CONFIG_CRYPTO_CHACHA20POLY1305=y
#
# Disabled subsystems
#
# CONFIG_SOUND is not set
# CONFIG_USB_SUPPORT is not set
# CONFIG_DRM is not set
# CONFIG_WIRELESS is not set
# CONFIG_WLAN is not set
# CONFIG_BLUETOOTH is not set
# CONFIG_INPUT_JOYSTICK is not set
# CONFIG_INPUT_TABLET is not set
# CONFIG_INPUT_TOUCHSCREEN is not set
# CONFIG_MEDIA_SUPPORT is not set
# CONFIG_AGP is not set
# CONFIG_PCMCIA is not set
# CONFIG_INFINIBAND is not set
# CONFIG_ISDN is not set
# CONFIG_PARPORT is not set
# CONFIG_PHONE is not set
# CONFIG_ACCESSIBILITY is not set
# CONFIG_LOGO is not set
# CONFIG_FB is not set
# CONFIG_BACKLIGHT_CLASS_DEVICE is not set
#
# Debugging — completely disabled for speed
#
CONFIG_PRINTK_TIME=y
CONFIG_CONSOLE_LOGLEVEL_DEFAULT=1
# CONFIG_MAGIC_SYSRQ is not set
# CONFIG_DEBUG_KERNEL is not set
# CONFIG_DEBUG_INFO_DWARF5 is not set
# CONFIG_KPROBES is not set
# CONFIG_FTRACE is not set
"#;
/// Required config options for the ultra-fast boot kernel.
pub const ULTRAFAST_REQUIRED_OPTIONS: &[&str] = &[
"CONFIG_64BIT=y",
"CONFIG_SMP=y",
"CONFIG_VIRTIO_PCI=y",
"CONFIG_VIRTIO_BLK=y",
"CONFIG_VIRTIO_NET=y",
"CONFIG_BPF=y",
"CONFIG_BPF_JIT=y",
"CONFIG_BPF_SYSCALL=y",
"CONFIG_VSOCKETS=y",
"CONFIG_VIRTIO_VSOCKETS=y",
"CONFIG_SECURITY_LOCKDOWN_LSM=y",
"CONFIG_STACKPROTECTOR_STRONG=y",
"CONFIG_RANDOMIZE_BASE=y",
"CONFIG_PREEMPT_NONE=y",
"CONFIG_NO_HZ_FULL=y",
"# CONFIG_MODULES is not set",
"# CONFIG_SOUND is not set",
"# CONFIG_USB_SUPPORT is not set",
"# CONFIG_DRM is not set",
"# CONFIG_WIRELESS is not set",
"# CONFIG_CGROUPS is not set",
"# CONFIG_NUMA is not set",
"# CONFIG_EXT4_FS is not set",
"# CONFIG_DEBUG_KERNEL is not set",
"CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y",
];
/// Required config options that MUST be present for a valid RVF microVM kernel.
///
/// These are checked by `validate_config()` to ensure the config wasn't
/// accidentally stripped of critical options.
pub const REQUIRED_OPTIONS: &[&str] = &[
"CONFIG_64BIT=y",
"CONFIG_SMP=y",
"CONFIG_VIRTIO_PCI=y",
"CONFIG_VIRTIO_BLK=y",
"CONFIG_VIRTIO_NET=y",
"CONFIG_BPF=y",
"CONFIG_BPF_JIT=y",
"CONFIG_BPF_SYSCALL=y",
"CONFIG_VSOCKETS=y",
"CONFIG_VIRTIO_VSOCKETS=y",
"CONFIG_EXT4_FS=y",
"CONFIG_SECURITY_LOCKDOWN_LSM=y",
"CONFIG_STACKPROTECTOR_STRONG=y",
"CONFIG_RANDOMIZE_BASE=y",
"CONFIG_PREEMPT_NONE=y",
"CONFIG_NO_HZ_FULL=y",
"# CONFIG_MODULES is not set",
"# CONFIG_SOUND is not set",
"# CONFIG_USB_SUPPORT is not set",
"# CONFIG_DRM is not set",
"# CONFIG_WIRELESS is not set",
];
/// Validate that a kernel config string contains all required options.
///
/// Returns `Ok(())` if all required options are present, or `Err` with
/// a list of missing options.
pub fn validate_config(config: &str) -> Result<(), Vec<&'static str>> {
let missing: Vec<&str> = REQUIRED_OPTIONS
.iter()
.filter(|&&opt| !config.lines().any(|line| line.trim() == opt))
.copied()
.collect();
if missing.is_empty() {
Ok(())
} else {
Err(missing)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn microvm_config_has_all_required_options() {
let result = validate_config(MICROVM_KERNEL_CONFIG);
assert!(
result.is_ok(),
"missing required options: {:?}",
result.unwrap_err()
);
}
#[test]
fn config_disables_modules() {
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_MODULES is not set"));
}
#[test]
fn config_enables_virtio() {
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_VIRTIO_PCI=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_VIRTIO_BLK=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_VIRTIO_NET=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_VIRTIO_VSOCKETS=y"));
}
#[test]
fn config_enables_bpf() {
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_BPF=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_BPF_JIT=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_BPF_SYSCALL=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_BPF_JIT_ALWAYS_ON=y"));
}
#[test]
fn config_enables_security_hardening() {
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_SECURITY_LOCKDOWN_LSM=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_STACKPROTECTOR_STRONG=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_RANDOMIZE_BASE=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_SECCOMP=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_SECCOMP_FILTER=y"));
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_FORTIFY_SOURCE=y"));
}
#[test]
fn config_disables_desktop_hardware() {
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_SOUND is not set"));
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_USB_SUPPORT is not set"));
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_DRM is not set"));
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_WIRELESS is not set"));
assert!(MICROVM_KERNEL_CONFIG.contains("# CONFIG_BLUETOOTH is not set"));
}
#[test]
fn validate_catches_missing_options() {
let incomplete = "CONFIG_64BIT=y\nCONFIG_SMP=y\n";
let result = validate_config(incomplete);
assert!(result.is_err());
let missing = result.unwrap_err();
assert!(missing.contains(&"CONFIG_VIRTIO_PCI=y"));
}
#[test]
fn ultrafast_config_has_all_required_options() {
let missing: Vec<&str> = ULTRAFAST_REQUIRED_OPTIONS
.iter()
.filter(|&&opt| !ULTRAFAST_BOOT_CONFIG.lines().any(|line| line.trim() == opt))
.copied()
.collect();
assert!(
missing.is_empty(),
"ultrafast config missing required options: {:?}",
missing
);
}
#[test]
fn ultrafast_config_disables_heavy_subsystems() {
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_CGROUPS is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_NAMESPACES is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_NUMA is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_AUDIT is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_EXT4_FS is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_NETFILTER is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_IPV6 is not set"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("# CONFIG_DEBUG_KERNEL is not set"));
}
#[test]
fn ultrafast_config_optimizes_for_performance() {
assert!(ULTRAFAST_BOOT_CONFIG.contains("CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("CONFIG_NR_CPUS=4"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("CONFIG_RD_LZ4=y"));
assert!(ULTRAFAST_BOOT_CONFIG.contains("CONFIG_CONSOLE_LOGLEVEL_DEFAULT=1"));
}
#[test]
fn ultrafast_config_is_nonzero_length() {
assert!(ULTRAFAST_BOOT_CONFIG.len() > 500);
}
#[test]
fn config_sets_localversion() {
assert!(MICROVM_KERNEL_CONFIG.contains("CONFIG_LOCALVERSION=\"-rvf\""));
}
#[test]
fn config_is_nonzero_length() {
assert!(MICROVM_KERNEL_CONFIG.len() > 1000);
}
}

View File

@@ -0,0 +1,259 @@
//! Docker-based kernel build support.
//!
//! Provides a real, working Dockerfile for building a minimal Linux kernel
//! from source inside Docker. This enables reproducible, CI-friendly kernel
//! builds without requiring a local toolchain.
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::config::MICROVM_KERNEL_CONFIG;
use crate::error::KernelError;
/// Default Linux kernel version to build.
pub const DEFAULT_KERNEL_VERSION: &str = "6.8.12";
/// Generate the Dockerfile content for building a Linux kernel.
///
/// The Dockerfile:
/// 1. Starts from Alpine 3.19 (small, fast package install)
/// 2. Installs the full GCC build toolchain + kernel build dependencies
/// 3. Downloads the specified kernel version from kernel.org
/// 4. Copies the RVF microVM kernel config
/// 5. Runs `make olddefconfig` to fill in defaults, then builds bzImage
/// 6. Multi-stage build: final image contains only the bzImage
pub fn generate_dockerfile(kernel_version: &str) -> String {
format!(
r#"# RVF MicroVM Kernel Builder
# Builds a minimal Linux kernel for Firecracker / QEMU microvm
# Generated by rvf-kernel
FROM alpine:3.19 AS builder
# Install kernel build dependencies
RUN apk add --no-cache \
build-base \
linux-headers \
bc \
flex \
bison \
elfutils-dev \
openssl-dev \
perl \
python3 \
cpio \
gzip \
wget \
xz
# Download and extract kernel source
ARG KERNEL_VERSION={kernel_version}
RUN wget -q "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-${{KERNEL_VERSION}}.tar.xz" && \
tar xf "linux-${{KERNEL_VERSION}}.tar.xz" && \
rm "linux-${{KERNEL_VERSION}}.tar.xz"
# Copy kernel configuration
COPY kernel.config "linux-${{KERNEL_VERSION}}/.config"
WORKDIR "/linux-${{KERNEL_VERSION}}"
# Apply config defaults and build
RUN make olddefconfig && \
make -j"$(nproc)" bzImage
# Extract just the bzImage in a scratch stage
FROM scratch
COPY --from=builder "/linux-{kernel_version}/arch/x86/boot/bzImage" /bzImage
"#
)
}
/// Context directory structure for a Docker kernel build.
pub struct DockerBuildContext {
/// Path to the build context directory.
pub context_dir: PathBuf,
/// Kernel version being built.
pub kernel_version: String,
}
impl DockerBuildContext {
/// Prepare a Docker build context directory with the Dockerfile and kernel config.
///
/// Creates:
/// - `<context_dir>/Dockerfile`
/// - `<context_dir>/kernel.config`
pub fn prepare(context_dir: &Path, kernel_version: Option<&str>) -> Result<Self, KernelError> {
let version = kernel_version.unwrap_or(DEFAULT_KERNEL_VERSION);
std::fs::create_dir_all(context_dir)?;
// Write Dockerfile
let dockerfile = generate_dockerfile(version);
std::fs::write(context_dir.join("Dockerfile"), dockerfile)?;
// Write kernel config
std::fs::write(context_dir.join("kernel.config"), MICROVM_KERNEL_CONFIG)?;
Ok(Self {
context_dir: context_dir.to_path_buf(),
kernel_version: version.to_string(),
})
}
/// Execute the Docker build and extract the resulting bzImage.
///
/// Requires Docker to be installed and accessible. The build may take
/// 10-30 minutes depending on CPU and network speed.
///
/// Returns the bzImage bytes on success.
pub fn build(&self) -> Result<Vec<u8>, KernelError> {
let image_tag = format!("rvf-kernel-build:{}", self.kernel_version);
// Build the Docker image
let build_status = Command::new("docker")
.args([
"build",
"-t",
&image_tag,
"--build-arg",
&format!("KERNEL_VERSION={}", self.kernel_version),
".",
])
.current_dir(&self.context_dir)
.status()
.map_err(|e| KernelError::DockerBuildFailed(format!("failed to run docker: {e}")))?;
if !build_status.success() {
return Err(KernelError::DockerBuildFailed(format!(
"docker build exited with status {}",
build_status
)));
}
// Clean up any leftover container from a previous run
let _ = Command::new("docker")
.args(["rm", "-f", "rvf-kernel-extract"])
.output();
// Create a temporary container to copy out the bzImage.
// The image is FROM scratch (no shell), so we pass a dummy
// entrypoint that won't be executed — docker create only
// creates the container filesystem, it doesn't run anything.
let create_output = Command::new("docker")
.args([
"create",
"--name",
"rvf-kernel-extract",
"--entrypoint",
"",
&image_tag,
"/bzImage",
])
.output()
.map_err(|e| KernelError::DockerBuildFailed(format!("docker create failed: {e}")))?;
if !create_output.status.success() {
let stderr = String::from_utf8_lossy(&create_output.stderr);
return Err(KernelError::DockerBuildFailed(format!(
"docker create failed: {stderr}"
)));
}
let bzimage_path = self.context_dir.join("bzImage");
let cp_status = Command::new("docker")
.args([
"cp",
"rvf-kernel-extract:/bzImage",
&bzimage_path.to_string_lossy(),
])
.status()
.map_err(|e| KernelError::DockerBuildFailed(format!("docker cp failed: {e}")))?;
// Clean up the temporary container (best-effort)
let _ = Command::new("docker")
.args(["rm", "rvf-kernel-extract"])
.status();
if !cp_status.success() {
return Err(KernelError::DockerBuildFailed(
"docker cp failed to extract bzImage".into(),
));
}
let bzimage = std::fs::read(&bzimage_path)?;
if bzimage.is_empty() {
return Err(KernelError::DockerBuildFailed(
"extracted bzImage is empty".into(),
));
}
Ok(bzimage)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn dockerfile_contains_required_steps() {
let dockerfile = generate_dockerfile(DEFAULT_KERNEL_VERSION);
// Must start from Alpine
assert!(dockerfile.contains("FROM alpine:3.19 AS builder"));
// Must install build dependencies
assert!(dockerfile.contains("build-base"));
assert!(dockerfile.contains("flex"));
assert!(dockerfile.contains("bison"));
assert!(dockerfile.contains("elfutils-dev"));
assert!(dockerfile.contains("openssl-dev"));
// Must download kernel source
assert!(dockerfile.contains("cdn.kernel.org"));
assert!(dockerfile.contains(DEFAULT_KERNEL_VERSION));
// Must copy config
assert!(dockerfile.contains("COPY kernel.config"));
// Must run make
assert!(dockerfile.contains("make olddefconfig"));
assert!(dockerfile.contains("make -j"));
assert!(dockerfile.contains("bzImage"));
// Must use multi-stage build
assert!(dockerfile.contains("FROM scratch"));
assert!(dockerfile.contains("COPY --from=builder"));
}
#[test]
fn dockerfile_uses_custom_version() {
let dockerfile = generate_dockerfile("6.9.1");
assert!(dockerfile.contains("6.9.1"));
}
#[test]
fn prepare_creates_files() {
let dir = tempfile::TempDir::new().unwrap();
let ctx = DockerBuildContext::prepare(dir.path(), None).unwrap();
assert!(dir.path().join("Dockerfile").exists());
assert!(dir.path().join("kernel.config").exists());
assert_eq!(ctx.kernel_version, DEFAULT_KERNEL_VERSION);
// Verify kernel.config content
let config = std::fs::read_to_string(dir.path().join("kernel.config")).unwrap();
assert!(config.contains("CONFIG_64BIT=y"));
assert!(config.contains("CONFIG_VIRTIO_PCI=y"));
}
#[test]
fn prepare_with_custom_version() {
let dir = tempfile::TempDir::new().unwrap();
let ctx = DockerBuildContext::prepare(dir.path(), Some("6.6.30")).unwrap();
assert_eq!(ctx.kernel_version, "6.6.30");
let dockerfile = std::fs::read_to_string(dir.path().join("Dockerfile")).unwrap();
assert!(dockerfile.contains("6.6.30"));
}
}

View File

@@ -0,0 +1,83 @@
//! Error types for the rvf-kernel crate.
use std::fmt;
use std::io;
use std::path::PathBuf;
/// Errors that can occur during kernel building, verification, or embedding.
#[derive(Debug)]
pub enum KernelError {
/// I/O error reading or writing kernel artifacts.
Io(io::Error),
/// The file at the given path is not a valid kernel image.
InvalidImage { path: PathBuf, reason: String },
/// The kernel image is too small to contain required headers.
ImageTooSmall { size: u64, min_size: u64 },
/// SHA3-256 hash of extracted kernel does not match the stored hash.
HashMismatch {
expected: [u8; 32],
actual: [u8; 32],
},
/// Docker is not available or the build failed.
DockerBuildFailed(String),
/// The initramfs archive could not be built.
InitramfsBuildFailed(String),
/// Compression or decompression failed.
CompressionFailed(String),
/// A required configuration option is missing.
MissingConfig(String),
/// The kernel config string is invalid or missing required options.
InvalidConfig(String),
/// No KERNEL_SEG found in the RVF store.
NoKernelSegment,
}
impl fmt::Display for KernelError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(e) => write!(f, "kernel I/O error: {e}"),
Self::InvalidImage { path, reason } => {
write!(f, "invalid kernel image at {}: {reason}", path.display())
}
Self::ImageTooSmall { size, min_size } => {
write!(
f,
"kernel image too small: {size} bytes (minimum {min_size})"
)
}
Self::HashMismatch { expected, actual } => {
write!(
f,
"kernel hash mismatch: expected {}..., got {}...",
hex_prefix(expected),
hex_prefix(actual)
)
}
Self::DockerBuildFailed(msg) => write!(f, "Docker kernel build failed: {msg}"),
Self::InitramfsBuildFailed(msg) => write!(f, "initramfs build failed: {msg}"),
Self::CompressionFailed(msg) => write!(f, "compression failed: {msg}"),
Self::MissingConfig(msg) => write!(f, "missing kernel config: {msg}"),
Self::InvalidConfig(msg) => write!(f, "invalid kernel config: {msg}"),
Self::NoKernelSegment => write!(f, "no KERNEL_SEG found in RVF store"),
}
}
}
impl std::error::Error for KernelError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Io(e) => Some(e),
_ => None,
}
}
}
impl From<io::Error> for KernelError {
fn from(e: io::Error) -> Self {
Self::Io(e)
}
}
fn hex_prefix(data: &[u8; 32]) -> String {
data.iter().take(4).map(|b| format!("{b:02x}")).collect()
}

View File

@@ -0,0 +1,671 @@
//! Real initramfs builder producing valid cpio archives (newc format).
//!
//! The cpio "newc" (SVR4 with no CRC) format is what the Linux kernel expects
//! for initramfs archives. Each entry consists of a 110-byte ASCII header
//! followed by the filename (NUL-terminated, padded to 4-byte boundary),
//! followed by the file data (padded to 4-byte boundary).
//!
//! The archive is terminated by a trailer entry with the name "TRAILER!!!".
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
use crate::error::KernelError;
/// Magic number for cpio newc format headers.
const CPIO_NEWC_MAGIC: &str = "070701";
/// Default /init script that mounts essential filesystems, configures
/// networking, and starts services.
pub fn default_init_script(services: &[&str]) -> String {
let mut script = String::from(
r#"#!/bin/sh
# RVF initramfs init script
# Generated by rvf-kernel
set -e
echo "RVF initramfs booting..."
# Mount essential filesystems
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev
mkdir -p /dev/pts /dev/shm
mount -t devpts devpts /dev/pts
mount -t tmpfs tmpfs /dev/shm
mount -t tmpfs tmpfs /tmp
mount -t tmpfs tmpfs /run
# Set hostname
hostname rvf
# Configure loopback
ip link set lo up
# Configure primary network interface
for iface in eth0 ens3 enp0s3; do
if [ -d "/sys/class/net/$iface" ]; then
ip link set "$iface" up
# Try DHCP if udhcpc is available
if command -v udhcpc >/dev/null 2>&1; then
udhcpc -i "$iface" -s /etc/udhcpc/simple.script -q &
fi
break
fi
done
# Set up minimal /etc
echo "root:x:0:0:root:/root:/bin/sh" > /etc/passwd
echo "root:x:0:" > /etc/group
echo "nameserver 8.8.8.8" > /etc/resolv.conf
echo "rvf" > /etc/hostname
"#,
);
// Start requested services
for service in services {
script.push_str(&format!(
"# Start service: {service}\n\
echo \"Starting {service}...\"\n"
));
match *service {
"sshd" | "dropbear" => {
script.push_str(
"mkdir -p /etc/dropbear\n\
if command -v dropbear >/dev/null 2>&1; then\n\
dropbear -R -F -E -p 2222 &\n\
elif command -v sshd >/dev/null 2>&1; then\n\
mkdir -p /etc/ssh\n\
ssh-keygen -A 2>/dev/null || true\n\
/usr/sbin/sshd -p 2222\n\
fi\n",
);
}
"rvf-server" => {
script.push_str(
"if command -v rvf-server >/dev/null 2>&1; then\n\
rvf-server --listen 0.0.0.0:8080 &\n\
fi\n",
);
}
other => {
script.push_str(&format!(
"if command -v {other} >/dev/null 2>&1; then\n\
{other} &\n\
fi\n"
));
}
}
script.push('\n');
}
script.push_str(
"echo \"RVF initramfs ready.\"\n\
\n\
# Drop to shell or wait\n\
exec /bin/sh\n",
);
script
}
/// A builder for creating cpio newc archives suitable for Linux initramfs.
pub struct CpioBuilder {
/// Accumulated cpio archive bytes (uncompressed).
data: Vec<u8>,
/// Monotonically increasing inode counter.
next_ino: u32,
}
impl CpioBuilder {
/// Create a new, empty cpio archive builder.
pub fn new() -> Self {
Self {
data: Vec::with_capacity(64 * 1024),
next_ino: 1,
}
}
/// Add a directory entry to the archive.
///
/// `path` must not include a leading `/` in the archive (e.g., "bin", "etc").
/// Mode 0o755 is used for directories.
pub fn add_dir(&mut self, path: &str) {
self.add_entry(path, 0o040755, &[]);
}
/// Add a regular file to the archive.
///
/// `path` is the archive-internal path (e.g., "init", "bin/busybox").
/// `mode` is the Unix file mode (e.g., 0o100755 for executable).
pub fn add_file(&mut self, path: &str, mode: u32, content: &[u8]) {
self.add_entry(path, mode, content);
}
/// Add a symlink to the archive.
///
/// The symlink `path` will point to `target`.
pub fn add_symlink(&mut self, path: &str, target: &str) {
self.add_entry(path, 0o120777, target.as_bytes());
}
/// Add a device node (character or block).
///
/// `mode` should include the device type bits (e.g., 0o020666 for char device).
/// `devmajor` and `devminor` are the device major/minor numbers.
pub fn add_device(&mut self, path: &str, mode: u32, devmajor: u32, devminor: u32) {
let ino = self.next_ino;
self.next_ino += 1;
let name_with_nul = format!("{path}\0");
let name_len = name_with_nul.len() as u32;
let filesize = 0u32;
let header = format!(
"{CPIO_NEWC_MAGIC}\
{ino:08X}\
{mode:08X}\
{uid:08X}\
{gid:08X}\
{nlink:08X}\
{mtime:08X}\
{filesize:08X}\
{devmajor:08X}\
{devminor:08X}\
{rdevmajor:08X}\
{rdevminor:08X}\
{namesize:08X}\
{check:08X}",
uid = 0u32,
gid = 0u32,
nlink = 1u32,
mtime = 0u32,
rdevmajor = devmajor,
rdevminor = devminor,
namesize = name_len,
check = 0u32,
);
self.data.extend_from_slice(header.as_bytes());
self.data.extend_from_slice(name_with_nul.as_bytes());
pad4(&mut self.data);
// No file data for device nodes
}
/// Finalize the archive by appending the TRAILER!!! entry.
///
/// Returns the raw (uncompressed) cpio archive bytes.
pub fn finish(mut self) -> Vec<u8> {
self.add_entry("TRAILER!!!", 0, &[]);
self.data
}
/// Finalize and gzip-compress the archive.
///
/// Returns the gzipped cpio archive suitable for use as a Linux initramfs.
pub fn finish_gzipped(self) -> Result<Vec<u8>, KernelError> {
let raw = self.finish();
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
encoder
.write_all(&raw)
.map_err(|e| KernelError::CompressionFailed(e.to_string()))?;
encoder
.finish()
.map_err(|e| KernelError::CompressionFailed(e.to_string()))
}
fn add_entry(&mut self, path: &str, mode: u32, content: &[u8]) {
let ino = self.next_ino;
self.next_ino += 1;
let name_with_nul = format!("{path}\0");
let name_len = name_with_nul.len() as u32;
let filesize = content.len() as u32;
// cpio newc header is 110 bytes of ASCII hex fields
let header = format!(
"{CPIO_NEWC_MAGIC}\
{ino:08X}\
{mode:08X}\
{uid:08X}\
{gid:08X}\
{nlink:08X}\
{mtime:08X}\
{filesize:08X}\
{devmajor:08X}\
{devminor:08X}\
{rdevmajor:08X}\
{rdevminor:08X}\
{namesize:08X}\
{check:08X}",
uid = 0u32,
gid = 0u32,
nlink = if mode & 0o040000 != 0 { 2u32 } else { 1u32 },
mtime = 0u32,
devmajor = 0u32,
devminor = 0u32,
rdevmajor = 0u32,
rdevminor = 0u32,
namesize = name_len,
check = 0u32,
);
debug_assert_eq!(header.len(), 110);
self.data.extend_from_slice(header.as_bytes());
self.data.extend_from_slice(name_with_nul.as_bytes());
pad4(&mut self.data);
if !content.is_empty() {
self.data.extend_from_slice(content);
pad4(&mut self.data);
}
}
}
impl Default for CpioBuilder {
fn default() -> Self {
Self::new()
}
}
/// Pad `data` to the next 4-byte boundary with NUL bytes.
fn pad4(data: &mut Vec<u8>) {
let rem = data.len() % 4;
if rem != 0 {
let padding = 4 - rem;
data.extend(std::iter::repeat_n(0u8, padding));
}
}
/// Build a complete initramfs with standard directory structure and an /init script.
///
/// `services` are the names of services to start in the init script
/// (e.g., "sshd", "rvf-server").
///
/// `extra_binaries` are (archive_path, content) pairs for additional binaries
/// to include (e.g., ("bin/busybox", &busybox_bytes)).
///
/// Returns a gzipped cpio archive.
pub fn build_initramfs(
services: &[&str],
extra_binaries: &[(&str, &[u8])],
) -> Result<Vec<u8>, KernelError> {
let mut cpio = CpioBuilder::new();
// Create directory structure
let dirs = [
".",
"bin",
"sbin",
"etc",
"etc/udhcpc",
"dev",
"proc",
"sys",
"tmp",
"var",
"var/log",
"var/run",
"run",
"root",
"lib",
"usr",
"usr/bin",
"usr/sbin",
"usr/lib",
"mnt",
"opt",
];
for dir in &dirs {
cpio.add_dir(dir);
}
// Create essential device nodes
cpio.add_device("dev/console", 0o020600, 5, 1);
cpio.add_device("dev/ttyS0", 0o020660, 4, 64);
cpio.add_device("dev/null", 0o020666, 1, 3);
cpio.add_device("dev/zero", 0o020666, 1, 5);
cpio.add_device("dev/urandom", 0o020444, 1, 9);
// Create /init script
let init_script = default_init_script(services);
cpio.add_file("init", 0o100755, init_script.as_bytes());
// Create a minimal udhcpc script
let udhcpc_script = r#"#!/bin/sh
case "$1" in
bound|renew)
ip addr add "$ip/$mask" dev "$interface"
if [ -n "$router" ]; then
ip route add default via "$router"
fi
if [ -n "$dns" ]; then
echo "nameserver $dns" > /etc/resolv.conf
fi
;;
esac
"#;
cpio.add_file(
"etc/udhcpc/simple.script",
0o100755,
udhcpc_script.as_bytes(),
);
// Add extra binaries
for (path, content) in extra_binaries {
cpio.add_file(path, 0o100755, content);
}
cpio.finish_gzipped()
}
/// Build an ultra-fast boot initramfs optimized for minimal startup time.
///
/// Compared to `build_initramfs`, this:
/// - Skips network interface enumeration/DHCP
/// - Mounts only /proc, /sys, /dev (no /dev/pts, /dev/shm, /tmp, /run)
/// - No /etc setup (no passwd, resolv.conf, hostname)
/// - Starts services immediately without probing
/// - Uses minimal directory structure
///
/// Target: kernel-to-service in under 50ms of userspace init time.
pub fn build_fast_initramfs(
services: &[&str],
extra_binaries: &[(&str, &[u8])],
) -> Result<Vec<u8>, KernelError> {
let mut cpio = CpioBuilder::new();
// Minimal directory structure
let dirs = [".", "bin", "sbin", "dev", "proc", "sys", "tmp", "run"];
for dir in &dirs {
cpio.add_dir(dir);
}
// Essential device nodes only
cpio.add_device("dev/console", 0o020600, 5, 1);
cpio.add_device("dev/ttyS0", 0o020660, 4, 64);
cpio.add_device("dev/null", 0o020666, 1, 3);
cpio.add_device("dev/urandom", 0o020444, 1, 9);
// Ultra-fast /init script
let mut script = String::from(
"#!/bin/sh\n\
mount -t proc proc /proc\n\
mount -t sysfs sysfs /sys\n\
mount -t devtmpfs devtmpfs /dev\n",
);
for service in services {
match *service {
"sshd" | "dropbear" => {
script.push_str(
"mkdir -p /etc/dropbear\n\
dropbear -R -F -E -p 2222 &\n",
);
}
"rvf-server" => {
script.push_str("rvf-server --listen 0.0.0.0:8080 &\n");
}
other => {
script.push_str(&format!("{other} &\n"));
}
}
}
script.push_str("exec /bin/sh\n");
cpio.add_file("init", 0o100755, script.as_bytes());
// Add extra binaries
for (path, content) in extra_binaries {
cpio.add_file(path, 0o100755, content);
}
cpio.finish_gzipped()
}
/// Parse a cpio newc archive and return the list of entries.
///
/// Each entry is returned as (path, mode, filesize, data_offset_in_archive).
/// This is primarily used for testing/verification.
pub fn parse_cpio_entries(data: &[u8]) -> Result<Vec<(String, u32, u32)>, KernelError> {
let mut entries = Vec::new();
let mut offset = 0;
loop {
if offset + 110 > data.len() {
break;
}
let header = &data[offset..offset + 110];
let header_str = std::str::from_utf8(header).map_err(|_| {
KernelError::InitramfsBuildFailed("invalid cpio header encoding".into())
})?;
// Verify magic
if &header_str[..6] != CPIO_NEWC_MAGIC {
return Err(KernelError::InitramfsBuildFailed(format!(
"invalid cpio magic at offset {offset}: {:?}",
&header_str[..6]
)));
}
let mode = u32::from_str_radix(&header_str[14..22], 16)
.map_err(|_| KernelError::InitramfsBuildFailed("invalid mode".into()))?;
let filesize = u32::from_str_radix(&header_str[54..62], 16)
.map_err(|_| KernelError::InitramfsBuildFailed("invalid filesize".into()))?;
let namesize = u32::from_str_radix(&header_str[94..102], 16)
.map_err(|_| KernelError::InitramfsBuildFailed("invalid namesize".into()))?;
// Name starts right after the 110-byte header
let name_start = offset + 110;
let name_end = name_start + namesize as usize;
if name_end > data.len() {
break;
}
let name_bytes = &data[name_start..name_end];
// Strip trailing NUL
let name = std::str::from_utf8(name_bytes)
.map_err(|_| KernelError::InitramfsBuildFailed("invalid name encoding".into()))?
.trim_end_matches('\0')
.to_string();
if name == "TRAILER!!!" {
break;
}
// Advance past header + name (padded to 4 bytes)
let after_name = align4(name_end);
// Advance past data (padded to 4 bytes)
let data_end = after_name + filesize as usize;
let next_entry = if filesize > 0 {
align4(data_end)
} else {
after_name
};
entries.push((name, mode, filesize));
offset = next_entry;
}
Ok(entries)
}
fn align4(val: usize) -> usize {
(val + 3) & !3
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cpio_builder_produces_valid_archive() {
let mut cpio = CpioBuilder::new();
cpio.add_dir("bin");
cpio.add_file("init", 0o100755, b"#!/bin/sh\necho hello\n");
cpio.add_symlink("bin/sh", "/bin/busybox");
let archive = cpio.finish();
// Verify it starts with the cpio magic
assert!(archive.starts_with(b"070701"));
// Parse it back
let entries = parse_cpio_entries(&archive).expect("parse should succeed");
assert_eq!(entries.len(), 3);
// Check directory
assert_eq!(entries[0].0, "bin");
assert_eq!(entries[0].1 & 0o040000, 0o040000); // is a directory
// Check file
assert_eq!(entries[1].0, "init");
assert_eq!(entries[1].1 & 0o100000, 0o100000); // is a regular file
assert_eq!(entries[1].1 & 0o755, 0o755); // executable
assert_eq!(entries[1].2, 21); // file size: "#!/bin/sh\necho hello\n"
// Check symlink
assert_eq!(entries[2].0, "bin/sh");
assert_eq!(entries[2].1 & 0o120000, 0o120000); // is a symlink
}
#[test]
fn cpio_archive_ends_with_trailer() {
let cpio = CpioBuilder::new();
let archive = cpio.finish();
let as_str = String::from_utf8_lossy(&archive);
assert!(as_str.contains("TRAILER!!!"));
}
#[test]
fn build_initramfs_produces_gzipped_cpio() {
let result = build_initramfs(&["sshd"], &[]).expect("build should succeed");
// gzip magic bytes
assert_eq!(result[0], 0x1F);
assert_eq!(result[1], 0x8B);
// Decompress and verify
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(&result[..]);
let mut decompressed = Vec::new();
decoder
.read_to_end(&mut decompressed)
.expect("decompress should succeed");
// Verify it's a valid cpio archive
let entries = parse_cpio_entries(&decompressed).expect("parse should succeed");
// Should have directories + devices + init + udhcpc script
assert!(
entries.len() >= 20,
"expected at least 20 entries, got {}",
entries.len()
);
// Check that /init exists
let init_entry = entries.iter().find(|(name, _, _)| name == "init");
assert!(init_entry.is_some(), "must have /init");
// Check that directories exist
let dir_names: Vec<&str> = entries
.iter()
.filter(|(_, mode, _)| mode & 0o040000 != 0)
.map(|(name, _, _)| name.as_str())
.collect();
assert!(dir_names.contains(&"bin"));
assert!(dir_names.contains(&"etc"));
assert!(dir_names.contains(&"proc"));
assert!(dir_names.contains(&"sys"));
assert!(dir_names.contains(&"dev"));
}
#[test]
fn build_initramfs_with_extra_binaries() {
let fake_binary = b"\x7FELF fake binary content";
let result = build_initramfs(&["rvf-server"], &[("bin/rvf-server", fake_binary)])
.expect("build should succeed");
// Decompress
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(&result[..]);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
let entries = parse_cpio_entries(&decompressed).unwrap();
let binary_entry = entries.iter().find(|(name, _, _)| name == "bin/rvf-server");
assert!(binary_entry.is_some(), "must have bin/rvf-server");
assert_eq!(binary_entry.unwrap().2, fake_binary.len() as u32);
}
#[test]
fn default_init_script_mounts_filesystems() {
let script = default_init_script(&[]);
assert!(script.contains("mount -t proc proc /proc"));
assert!(script.contains("mount -t sysfs sysfs /sys"));
assert!(script.contains("mount -t devtmpfs devtmpfs /dev"));
}
#[test]
fn default_init_script_includes_services() {
let script = default_init_script(&["sshd", "rvf-server"]);
assert!(script.contains("dropbear") || script.contains("sshd"));
assert!(script.contains("rvf-server"));
}
#[test]
fn cpio_header_is_110_bytes() {
let mut cpio = CpioBuilder::new();
cpio.add_file("x", 0o100644, b"");
let archive = cpio.finish();
// First entry header should be exactly 110 ASCII chars
let header_str = std::str::from_utf8(&archive[..110]).unwrap();
assert!(header_str.starts_with(CPIO_NEWC_MAGIC));
}
#[test]
fn build_fast_initramfs_is_smaller() {
let normal = build_initramfs(&["sshd", "rvf-server"], &[]).unwrap();
let fast = build_fast_initramfs(&["sshd", "rvf-server"], &[]).unwrap();
// Fast initramfs should be smaller (fewer dirs, shorter init script)
assert!(
fast.len() < normal.len(),
"fast ({}) should be smaller than normal ({})",
fast.len(),
normal.len()
);
// Both should be valid gzip
assert_eq!(fast[0], 0x1F);
assert_eq!(fast[1], 0x8B);
// Decompress and verify it has /init
use flate2::read::GzDecoder;
use std::io::Read;
let mut decoder = GzDecoder::new(&fast[..]);
let mut decompressed = Vec::new();
decoder.read_to_end(&mut decompressed).unwrap();
let entries = parse_cpio_entries(&decompressed).unwrap();
let has_init = entries.iter().any(|(name, _, _)| name == "init");
assert!(has_init, "fast initramfs must have /init");
}
#[test]
fn device_nodes_are_parseable() {
let mut cpio = CpioBuilder::new();
cpio.add_device("dev/null", 0o020666, 1, 3);
let archive = cpio.finish();
let entries = parse_cpio_entries(&archive).unwrap();
assert_eq!(entries.len(), 1);
assert_eq!(entries[0].0, "dev/null");
// Character device bit
assert_eq!(entries[0].1 & 0o020000, 0o020000);
}
}

View File

@@ -0,0 +1,861 @@
//! 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);
}
}