The original PVM kernel tree was based on Linux 6.12.x, while current Linux
master had reached 7.2-rc7. This note records the commands I used to build,
package, and boot the forward port. The resulting tree is available in the
pvm-720 branch.
PVM in four lines
PVM (Pagetable-based Virtual Machine) is a KVM guest hypervisor designed for
running nested workloads without exposing VMX or SVM to the guest. It uses a
small shared region for privilege transitions and efficient shadow page tables
for memory virtualization. The design is described in the SOSP '23 paper
PVM: Efficient Shadow Paging for Deploying Secure Containers in Cloud-native Environments.
The original code is in the virt-pvm/linux
repository.
Get the port
On Ubuntu 26.04:
sudo apt update
sudo apt install -y \
bc bison build-essential flex git libelf-dev libssl-dev
git clone --branch pvm-720 --single-branch \
https://github.com/kholia/linux.git linux-pvm
cd linux-pvm
git log -1 --oneline
make --silent kernelversion
The port updates the x86 entry code, KVM capability and MSR handling, PFN cache interfaces, early page tables, linker layout, and objtool annotations. It also adds a PVM KVM selftest, a minimal guest config, a config validator, and Ubuntu 26.04 GitHub Actions builds.
Build the minimal guest kernel
Start with tinyconfig, merge the PVM guest fragment, resolve Kconfig
dependencies, and only then run the validator:
make ARCH=x86 tinyconfig
scripts/kconfig/merge_config.sh -m \
.config kernel/configs/virt_pvm_openshell.config
make ARCH=x86 olddefconfig
scripts/check-pvm-config.sh .config
make --silent ARCH=x86 -j"$(nproc)" bzImage
ls -lh arch/x86/boot/bzImage
The order matters. Running scripts/check-pvm-config.sh before
olddefconfig can report selected or hidden options incorrectly.
The final kernel is built-in-only: there is no module loader or initramfs. It contains the virtio, ext4, namespace, cgroup, networking, netfilter, seccomp, and hardening features required by the guest workload.
Build an installable PVM host kernel
The host build starts with the default config of the running Ubuntu 26.04 kernel and merges the PVM host fragment. Debug information is disabled to keep the CI build and packages manageable. Ubuntu-specific certificate paths are also cleared because those files are not present in the upstream Linux tree.
Install the packaging dependencies:
sudo apt install -y --no-install-recommends \
bc bison build-essential cpio debhelper dpkg-dev dwarves fakeroot \
flex kmod libdw-dev libelf-dev libssl-dev python3 rsync xz-utils zstd
Create the host config:
ubuntu_config="/boot/config-$(uname -r)"
test -r "$ubuntu_config"
cp "$ubuntu_config" ubuntu-26.04-base.config
cp "$ubuntu_config" .config
scripts/config --file .config \
--set-str LOCALVERSION "" \
--disable LOCALVERSION_AUTO \
--set-str SYSTEM_TRUSTED_KEYS "" \
--set-str SYSTEM_REVOCATION_KEYS "" \
--disable DEBUG_INFO \
--enable DEBUG_INFO_NONE \
--disable DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT \
--disable DEBUG_INFO_DWARF4 \
--disable DEBUG_INFO_DWARF5 \
--disable DEBUG_INFO_BTF
scripts/kconfig/merge_config.sh -m \
.config kernel/configs/virt_pvm_test_host.config
make ARCH=x86 olddefconfig
grep -x 'CONFIG_KVM=y' .config
grep -x 'CONFIG_KVM_PVM=y' .config
grep -x 'CONFIG_KVM_WERROR=y' .config
grep -x 'CONFIG_DEBUG_INFO_NONE=y' .config
! grep -Eq '^CONFIG_KVM_(INTEL|AMD)=[ym]$' .config
Build the Debian packages:
export KBUILD_BUILD_HOST=local
export KBUILD_BUILD_USER=pvm
export KDEB_CHANGELOG_DIST=resolute
export KDEB_SOURCENAME=linux-pvm
export LOCALVERSION=-pvm
kernel_version="$(make --silent kernelversion | sed 's/-rc/~rc/')"
export KDEB_PKGVERSION="${kernel_version}+pvm.1"
make --silent ARCH=x86 -j"$(nproc)" bindeb-pkg
ls -lh ../linux-image-*.deb ../linux-headers-*.deb
Install both packages:
sudo apt install ../linux-image-*.deb ../linux-headers-*.deb
sudo update-grub
PVM currently requires pti=off on the host. Add it to
GRUB_CMDLINE_LINUX_DEFAULT in /etc/default/grub, then regenerate GRUB and
reboot:
sudoedit /etc/default/grub
sudo update-grub
sudo reboot
After rebooting:
uname -r
grep '^CONFIG_KVM_PVM=y' "/boot/config-$(uname -r)"
ls -l /dev/kvm
KVM_PVM is built into this host kernel, so there is no kvm_pvm module to
load.
Run the PVM selftest
cd linux-pvm
make -C tools/testing/selftests/kvm \
-j"$(nproc)" x86/pvm_test
./tools/testing/selftests/kvm/x86/pvm_test
The test covers PVM availability, virtual MSRs, register access, and invalid address handling. Run it on the newly built host kernel; an older 6.12 PVM host does not contain all the fixes tested by the new binary.
Download an Ubuntu 26.04 guest image
Install QEMU, qboot, and the cloud-image helper:
sudo apt install -y \
cloud-image-utils curl qemu-system-data qemu-system-x86
Download the current Resolute cloud image and verify it against Ubuntu's published checksum file:
curl -fLO \
https://cloud-images.ubuntu.com/resolute/current/resolute-server-cloudimg-amd64.img
curl -fLO \
https://cloud-images.ubuntu.com/resolute/current/SHA256SUMS
grep 'resolute-server-cloudimg-amd64.img$' SHA256SUMS | sha256sum -c -
Create a small NoCloud seed so the ubuntu user can log in on the serial
console. This password is only suitable for an isolated test VM:
cat > user-data <<'EOF'
#cloud-config
password: password
chpasswd: { expire: false }
ssh_pwauth: true
EOF
cloud-localds user-data.img user-data
Boot the new bzImage with qboot
SeaBIOS rejected an early write into its protected pc.bios region during
testing. qboot is small, fast, and works correctly for direct kernel boot. Its
ROM is installed by Ubuntu's qemu-system-data package.
export ROOTFS="$PWD/resolute-server-cloudimg-amd64.img"
export SEED="$PWD/user-data.img"
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-smp 2 \
-m 512M \
-bios /usr/share/qemu/qboot.rom \
-kernel arch/x86/boot/bzImage \
-append "console=ttyS0 root=/dev/vda1 rw rootwait pti=off panic=-1 fstab=no systemd.mask=lvm2-monitor.service systemd.mask=multipathd.service systemd.mask=multipathd.socket systemd.mask=chrony.service" \
-drive file="$ROOTFS",if=none,format=qcow2,id=root \
-device virtio-blk-pci,drive=root \
-drive file="$SEED",if=virtio,format=raw,readonly=on \
-snapshot \
-nographic \
-no-reboot \
-nic user,model=virtio-net-pci
The expected end state is:
Reached target multi-user.target
Reached target graphical.target
Ubuntu 26.04 LTS ubuntu ttyS0
ubuntu login: ubuntu
Password:
Welcome to Ubuntu 26.04 LTS (GNU/Linux 7.2.0-rc7+ x86_64)
...
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
ubuntu@ubuntu:~$ sudo dhcpcd enp0s2
ubuntu@ubuntu:~$ curl ifconfig.me/all
ip_addr: <ip-address>
user_agent: curl/8.18.0
Use Ctrl-a x to leave QEMU. -snapshot ensures that the downloaded cloud
image is not modified.
Build both kernels with GitHub Actions
The guest artifact contains bzImage and .config. The host artifact contains
the image and headers .deb packages, checksums, the untouched Ubuntu 26.04
base config, the final PVM config, and a short installation README.