|
| 1 | +# libkrun & libkrunfw: A Technical Deep Dive into Lightweight Virtualization |
| 2 | + |
| 3 | +> From Linux/macOS to Windows: How a Dynamic Library Makes Process Isolation Accessible |
| 4 | +
|
| 5 | +--- |
| 6 | + |
| 7 | +## Background: Why Lightweight Virtualization? |
| 8 | + |
| 9 | +Container technologies (like Docker) achieve process isolation through Linux namespaces and cgroups, but they share the host kernel, creating kernel escape security risks. Traditional virtual machines (like QEMU/KVM) provide strong isolation but are slow to start and resource-intensive, making them unsuitable for container scenarios. |
| 10 | + |
| 11 | +**libkrun** takes a middle path: using virtualization technology to provide kernel-level isolation while maintaining container-like lightweight characteristics. |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## What is libkrun? |
| 16 | + |
| 17 | +libkrun is a **lightweight VMM (Virtual Machine Monitor) in dynamic library form** that allows any program to gain hardware virtualization-based process isolation capabilities simply by linking to this library. |
| 18 | + |
| 19 | +``` |
| 20 | +Application (crun / krunkit / muvm / a3s box) |
| 21 | + ↓ links to |
| 22 | + libkrun.so / libkrun.dylib |
| 23 | + ↓ calls |
| 24 | + KVM (Linux) / HVF (macOS) / WHPX (Windows) |
| 25 | + ↓ runs |
| 26 | + Guest VM (isolated process) |
| 27 | +``` |
| 28 | + |
| 29 | +Its C API is extremely simple: |
| 30 | + |
| 31 | +```c |
| 32 | +// Create a VM context |
| 33 | +uint32_t ctx = krun_create_ctx(); |
| 34 | + |
| 35 | +// Configure resources |
| 36 | +krun_set_vm_config(ctx, 2, 512); // 2 vCPUs, 512MB memory |
| 37 | +krun_set_root(ctx, "/path/to/rootfs"); // Root filesystem |
| 38 | +krun_set_exec(ctx, "/bin/sh", args, env); // Program to run |
| 39 | + |
| 40 | +// Start VM (blocks until VM exits) |
| 41 | +krun_start_enter(ctx); |
| 42 | +``` |
| 43 | +
|
| 44 | +### Core Components |
| 45 | +
|
| 46 | +libkrun internally integrates a complete VMM, including: |
| 47 | +
|
| 48 | +| Component | Purpose | |
| 49 | +|-----------|---------| |
| 50 | +| vCPU Management | Create, run, destroy virtual CPUs | |
| 51 | +| Memory Management | Allocate guest physical memory | |
| 52 | +| Device Emulation | virtio devices (console, fs, net, block, etc.) | |
| 53 | +| Interrupt Controller | Emulate APIC/GIC | |
| 54 | +| Boot Loader | Load kernel into guest memory and boot | |
| 55 | +
|
| 56 | +--- |
| 57 | +
|
| 58 | +## What is libkrunfw? |
| 59 | +
|
| 60 | +libkrun needs a Linux kernel to run guest processes. **libkrunfw** is the carrier for this kernel—it packages a specially configured Linux kernel as a dynamic library. |
| 61 | +
|
| 62 | +### Clever Design |
| 63 | +
|
| 64 | +Traditional approaches require reading kernel files from disk, but libkrunfw leverages dynamic linker features: |
| 65 | +
|
| 66 | +``` |
| 67 | +libkrunfw.so.5 |
| 68 | +├── .data section: kernel binary image (vmlinux) |
| 69 | +├── krunfw_get_kernel(): returns pointer and size of kernel image |
| 70 | +└── krunfw_get_initrd(): returns initrd pointer (TEE variant) |
| 71 | +``` |
| 72 | +
|
| 73 | +When libkrun loads libkrunfw, the dynamic linker directly maps the kernel image into the process address space. libkrun gets the pointer and directly injects this memory into the guest's physical memory, without any file I/O. |
| 74 | +
|
| 75 | +```rust |
| 76 | +// Code in libkrun for loading libkrunfw |
| 77 | +static KRUNFW: LazyLock<Option<libloading::Library>> = |
| 78 | + LazyLock::new(|| unsafe { libloading::Library::new("libkrunfw.so.5").ok() }); |
| 79 | +
|
| 80 | +// Get kernel image pointer |
| 81 | +let get_kernel: Symbol<unsafe extern "C" fn(*mut u64, *mut u64, *mut size_t) -> *mut c_char> |
| 82 | + = krunfw.get(b"krunfw_get_kernel")?; |
| 83 | +``` |
| 84 | + |
| 85 | +### What's Special About the Kernel in libkrunfw? |
| 86 | + |
| 87 | +The kernel in libkrunfw is not a standard distribution kernel; it includes special patches: |
| 88 | + |
| 89 | +1. **TSI (Transparent Socket Impersonation) patches**: Allow the guest kernel to transparently forward socket calls to the VMM proxy, enabling network connectivity without virtual NICs. |
| 90 | +2. **Minimized configuration**: Removes many unnecessary drivers and features, reducing size and speeding up boot time. |
| 91 | +3. **CPU count limit**: `CONFIG_NR_CPUS=8`, optimized for lightweight scenarios. |
| 92 | + |
| 93 | +### Multiple Variants |
| 94 | + |
| 95 | +| Variant | Library Name | Purpose | |
| 96 | +|---------|--------------|---------| |
| 97 | +| Standard | `libkrunfw.so.5` | General virtualization | |
| 98 | +| SEV | `libkrunfw-sev.so.5` | AMD memory encryption | |
| 99 | +| TDX | `libkrunfw-tdx.so.5` | Intel Trust Domain Extensions | |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## Technical Value |
| 104 | + |
| 105 | +### 1. Extremely Low Barrier to Entry |
| 106 | + |
| 107 | +No need to understand KVM ioctls, QEMU configuration, or any virtualization details. A single C function call can start an isolated VM. This allows projects like [crun](https://github.com/containers/crun), [krunkit](https://github.com/containers/krunkit), and [muvm](https://github.com/AsahiLinux/muvm) to easily gain virtualization isolation capabilities. |
| 108 | + |
| 109 | +### 2. Fast Boot Time |
| 110 | + |
| 111 | +Since the kernel is already mapped into memory through the dynamic linker, disk read time is eliminated. Combined with the kernel's minimized configuration, the entire VM boot time can be controlled at the millisecond level. |
| 112 | + |
| 113 | +### 3. Strong Security Isolation |
| 114 | + |
| 115 | +Compared to namespace isolation, libkrun provides hardware-level isolation boundaries. Even if the guest kernel has vulnerabilities, attackers need to break through the hypervisor layer to affect the host. |
| 116 | + |
| 117 | +### 4. TSI Network Innovation |
| 118 | + |
| 119 | +Traditional VMs require virtual NICs + userspace network proxies (like passt/gvproxy) for networking, with complex configuration. TSI intercepts socket system calls in the guest kernel and transparently forwards network requests to the VMM, without any virtual network devices, greatly simplifying network configuration. |
| 120 | + |
| 121 | +### 5. Cross-Platform Capability |
| 122 | + |
| 123 | +The same API uses KVM on Linux, HVF on macOS, and WHPX on Windows, with upper-layer applications unaware of underlying differences. |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## Limitations |
| 128 | + |
| 129 | +### Technical Limitations |
| 130 | + |
| 131 | +**1. Dependency on Custom Kernel** |
| 132 | +Core features like TSI require the custom kernel in libkrunfw; standard distribution kernels cannot be used. This means kernel version and features are determined by libkrunfw, and users cannot freely choose. |
| 133 | + |
| 134 | +**2. Limited Workload Compatibility** |
| 135 | +libkrun is designed to run single processes, not general-purpose VMs. It does not support: |
| 136 | +- Workloads requiring special kernel modules |
| 137 | +- OS installations requiring UEFI/BIOS (except EFI variant) |
| 138 | +- Scenarios requiring PCI passthrough |
| 139 | + |
| 140 | +**3. CPU Count Ceiling** |
| 141 | +The libkrunfw kernel limits to a maximum of 8 vCPUs, unsuitable for high-concurrency compute-intensive scenarios. |
| 142 | + |
| 143 | +**4. TDX Variant Memory Limit** |
| 144 | +The Intel TDX variant supports a maximum of 3072MB memory and 1 vCPU, with significant limitations. |
| 145 | + |
| 146 | +### Security Model Limitations |
| 147 | + |
| 148 | +libkrun's security model treats the guest and VMM as the same security context. Host resources accessible to the VMM can theoretically be accessed by the guest through the VMM. To achieve true isolation, restrictions must be applied to the VMM process itself at the host level (such as Linux namespaces). |
| 149 | + |
| 150 | +### Platform Limitations |
| 151 | + |
| 152 | +- **Windows support is still experimental**: WHPX backend is under development |
| 153 | +- **macOS only supports ARM64**: x86_64 macOS is not supported (HVF limitation) |
| 154 | +- **Linux requires KVM support**: Cannot be used in VMs or environments without KVM |
| 155 | + |
| 156 | +--- |
| 157 | + |
| 158 | +## WHPX: Enabling libkrun on Windows |
| 159 | + |
| 160 | +### What is WHPX? |
| 161 | + |
| 162 | +**Windows Hypervisor Platform (WHPX)** is a userspace virtualization API introduced by Microsoft in Windows 10 version 2004, exposed to applications through `WinHvPlatform.dll`. It is the userspace interface to Hyper-V, similar to Linux's KVM and macOS's HVF. |
| 163 | + |
| 164 | +``` |
| 165 | +Windows Application |
| 166 | + ↓ |
| 167 | +WinHvPlatform API (userspace) |
| 168 | + ↓ |
| 169 | +Hyper-V Hypervisor (kernel) |
| 170 | + ↓ |
| 171 | +Hardware Virtualization (Intel VT-x / AMD-V) |
| 172 | +``` |
| 173 | + |
| 174 | +### WHPX Core APIs |
| 175 | + |
| 176 | +| API | Purpose | |
| 177 | +|-----|---------| |
| 178 | +| `WHvCreatePartition` | Create VM partition | |
| 179 | +| `WHvSetupPartition` | Configure partition parameters | |
| 180 | +| `WHvMapGpaRange` | Map guest physical memory | |
| 181 | +| `WHvCreateVirtualProcessor` | Create vCPU | |
| 182 | +| `WHvRunVirtualProcessor` | Run vCPU until VM exit | |
| 183 | +| `WHvGetVirtualProcessorRegisters` | Read vCPU registers | |
| 184 | +| `WHvSetVirtualProcessorRegisters` | Write vCPU registers | |
| 185 | +| `WHvDeleteVirtualProcessor` | Destroy vCPU | |
| 186 | +| `WHvDeletePartition` | Destroy partition | |
| 187 | + |
| 188 | +### VM Exit Handling Mechanism |
| 189 | + |
| 190 | +WHPX uses a **synchronous VM exit** model: each time the guest executes an operation requiring VMM intervention, `WHvRunVirtualProcessor` returns, the VMM handles it, and then calls again to continue execution. |
| 191 | + |
| 192 | +```rust |
| 193 | +// Core logic of WHPX vCPU run loop in libkrun |
| 194 | +pub fn run(&mut self) -> io::Result<VcpuExit<'_>> { |
| 195 | + let mut exit_context = WHV_RUN_VP_EXIT_CONTEXT::default(); |
| 196 | + |
| 197 | + unsafe { |
| 198 | + WHvRunVirtualProcessor( |
| 199 | + self.partition, |
| 200 | + self.index, |
| 201 | + &mut exit_context as *mut _, |
| 202 | + size_of::<WHV_RUN_VP_EXIT_CONTEXT>() as u32, |
| 203 | + )?; |
| 204 | + } |
| 205 | + |
| 206 | + // Parse exit reason |
| 207 | + match exit_context.ExitReason { |
| 208 | + WHV_RUN_VP_EXIT_REASON_MEMORY_ACCESS => { /* MMIO handling */ } |
| 209 | + WHV_RUN_VP_EXIT_REASON_X64_IO_PORT_ACCESS => { /* IO port handling */ } |
| 210 | + WHV_RUN_VP_EXIT_REASON_X64_HALT => Ok(VcpuExit::Halted), |
| 211 | + WHV_RUN_VP_EXIT_REASON_CANCELED => Ok(VcpuExit::Shutdown), |
| 212 | + _ => Ok(VcpuExit::Shutdown), |
| 213 | + } |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +### libkrun WHPX Backend Architecture |
| 218 | + |
| 219 | +To support libkrun on Windows, we implemented the following architecture: |
| 220 | + |
| 221 | +``` |
| 222 | +Vcpu::run() [vstate.rs] |
| 223 | + ↓ loops calling |
| 224 | +WhpxVcpu::run() [whpx_vcpu.rs] |
| 225 | + ↓ calls WHvRunVirtualProcessor |
| 226 | + ↓ returns VcpuExit enum |
| 227 | +Vcpu::run_emulation() |
| 228 | + ↓ dispatches based on exit type |
| 229 | + ├── MmioRead/MmioWrite → mmio_bus (device emulation) |
| 230 | + ├── IoPortRead/IoPortWrite → mmio_bus (IO port devices) |
| 231 | + ├── Halted → VcpuEmulation::Halted (stop) |
| 232 | + └── Shutdown → VcpuEmulation::Stopped (stop) |
| 233 | +``` |
| 234 | + |
| 235 | +The **VcpuExit enum** design uses Rust's lifetime parameters to ensure memory safety of MMIO/IO data buffers: |
| 236 | + |
| 237 | +```rust |
| 238 | +pub enum VcpuExit<'a> { |
| 239 | + MmioRead(u64, &'a mut [u8]), // address + mutable buffer (device fills data) |
| 240 | + MmioWrite(u64, &'a [u8]), // address + data |
| 241 | + IoPortRead(u16, &'a mut [u8]), // port + mutable buffer |
| 242 | + IoPortWrite(u16, &'a [u8]), // port + data |
| 243 | + Halted, |
| 244 | + Shutdown, |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +### Comparison with KVM/HVF |
| 249 | + |
| 250 | +| Feature | KVM (Linux) | HVF (macOS) | WHPX (Windows) | |
| 251 | +|---------|-------------|-------------|----------------| |
| 252 | +| API Level | Kernel ioctl | Userspace framework | Userspace DLL | |
| 253 | +| Memory Mapping | `KVM_SET_USER_MEMORY_REGION` | `hv_vm_map` | `WHvMapGpaRange` | |
| 254 | +| vCPU Execution | `KVM_RUN` ioctl | `hv_vcpu_run` | `WHvRunVirtualProcessor` | |
| 255 | +| Exit Information | `kvm_run` shared memory | `hv_vcpu_exit_t` | `WHV_RUN_VP_EXIT_CONTEXT` | |
| 256 | +| Register Access | `KVM_GET/SET_REGS` | `hv_vcpu_get/set_reg` | `WHvGet/SetVirtualProcessorRegisters` | |
| 257 | +| Minimum Requirements | Linux + KVM module | macOS 11+ ARM64 | Windows 10 2004+ + Hyper-V | |
| 258 | + |
| 259 | +### Significance of Windows Support |
| 260 | + |
| 261 | +The WHPX backend implementation makes libkrun a truly cross-platform virtualization library: |
| 262 | + |
| 263 | +1. **Windows Container Ecosystem**: Provides virtualization isolation capabilities for container runtimes on Windows (like Windows version of crun) |
| 264 | +2. **Development Environment Consistency**: Developers can use the same isolation mechanism on Windows as on Linux/macOS |
| 265 | +3. **a3s box Cross-Platform**: Enables a3s box to provide consistent security isolation experience on Windows as on other platforms |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +## Summary |
| 270 | + |
| 271 | +libkrun and libkrunfw together form an elegant lightweight virtualization solution: |
| 272 | + |
| 273 | +- **libkrunfw** solves the "where does the kernel come from" problem—packaging the kernel as a dynamic library, using the linker for zero-overhead loading |
| 274 | +- **libkrun** solves the "how to use virtualization" problem—wrapping complex VMM into a simple C API |
| 275 | +- **WHPX backend** solves the "what about Windows" problem—implementing virtualization capabilities equivalent to KVM/HVF through Windows Hypervisor Platform API |
| 276 | + |
| 277 | +This design philosophy—**minimization, self-containment, cross-platform**—makes libkrun an important infrastructure in the container security isolation field. |
| 278 | + |
| 279 | +--- |
| 280 | + |
| 281 | +*This article is based on libkrun source code analysis. Some implementation details are subject to the code.* |
0 commit comments