Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
683
vendor/ruvector/crates/rvf/rvf-kernel/src/config.rs
vendored
Normal file
683
vendor/ruvector/crates/rvf/rvf-kernel/src/config.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
259
vendor/ruvector/crates/rvf/rvf-kernel/src/docker.rs
vendored
Normal file
259
vendor/ruvector/crates/rvf/rvf-kernel/src/docker.rs
vendored
Normal 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"));
|
||||
}
|
||||
}
|
||||
83
vendor/ruvector/crates/rvf/rvf-kernel/src/error.rs
vendored
Normal file
83
vendor/ruvector/crates/rvf/rvf-kernel/src/error.rs
vendored
Normal 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()
|
||||
}
|
||||
671
vendor/ruvector/crates/rvf/rvf-kernel/src/initramfs.rs
vendored
Normal file
671
vendor/ruvector/crates/rvf/rvf-kernel/src/initramfs.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
861
vendor/ruvector/crates/rvf/rvf-kernel/src/lib.rs
vendored
Normal file
861
vendor/ruvector/crates/rvf/rvf-kernel/src/lib.rs
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user