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.

References