From cc40923e2379796997c6ac8fbdeb7f61cd571447 Mon Sep 17 00:00:00 2001 From: Konstantin Bogomolov Date: Wed, 8 Jul 2026 11:02:18 -0700 Subject: [PATCH] KVM: Dynamically size IPA and fault blocks on ARM64 KVM on ARM64 defaults to 40-bit IPA, but can be dynamically limited by the host. Previously, we assumed a fixed 40-bit IPA and used large fault block sizes (up to 4TB), which could lead to GPAs exceeding the guest's IPA space. This caused sentry to crash with "set memory region failed" (EFAULT/EINVAL) during platform initialization. Fix this by: 1. Dynamically detecting host max IPA and limiting PhysicalAddressBits (up to 40). 2. Reducing faultBlockSize to 1GB (from 8GB) on ARM64 if IPA is <= 39-bit to pack GPAs tighter. PiperOrigin-RevId: 944592838 --- pkg/ring0/aarch64.go | 10 ++++++++-- pkg/sentry/platform/kvm/kvm_amd64.go | 1 + pkg/sentry/platform/kvm/kvm_arm64.go | 13 +++++++++++++ pkg/sentry/platform/kvm/machine.go | 8 ++------ pkg/sentry/platform/kvm/machine_amd64.go | 14 ++++++++++++++ pkg/sentry/platform/kvm/machine_arm64.go | 13 +++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) diff --git a/pkg/ring0/aarch64.go b/pkg/ring0/aarch64.go index 2c357f59ee..5aa9218fad 100644 --- a/pkg/ring0/aarch64.go +++ b/pkg/ring0/aarch64.go @@ -19,10 +19,16 @@ package ring0 const ( // VirtualAddressBits is fixed at 48. VirtualAddressBits = 48 +) - // PhysicalAddressBits is fixed at 40. - PhysicalAddressBits = 40 +var ( + // PhysicalAddressBits is the number of bits available in the physical + // address space. On ARM64, this is dynamically limited by the host KVM IPA size, + // up to a maximum of 40 bits. + PhysicalAddressBits uintptr = 40 +) +const ( // DAIF bits:debug, sError, IRQ, FIQ. _PSR_D_BIT = 0x00000200 _PSR_A_BIT = 0x00000100 diff --git a/pkg/sentry/platform/kvm/kvm_amd64.go b/pkg/sentry/platform/kvm/kvm_amd64.go index af921c4da8..69e7fb39a6 100644 --- a/pkg/sentry/platform/kvm/kvm_amd64.go +++ b/pkg/sentry/platform/kvm/kvm_amd64.go @@ -248,6 +248,7 @@ func updateGlobalOnce(fd int) error { ring0.Init(cpuid.FeatureSet{ Function: s, }) + initFaultBlocks() physicalInit() return nil } diff --git a/pkg/sentry/platform/kvm/kvm_arm64.go b/pkg/sentry/platform/kvm/kvm_arm64.go index 8a53daf059..999496d223 100644 --- a/pkg/sentry/platform/kvm/kvm_arm64.go +++ b/pkg/sentry/platform/kvm/kvm_arm64.go @@ -20,7 +20,9 @@ package kvm import ( "fmt" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/hostsyscall" "gvisor.dev/gvisor/pkg/ring0" "gvisor.dev/gvisor/pkg/sentry/arch" ) @@ -66,7 +68,18 @@ type kvmVcpuEvents struct { // updateGlobalOnce does global initialization. It has to be called only once. func updateGlobalOnce(fd int) error { err := updateSystemValues(int(fd)) + + maxIPA, errno := hostsyscall.RawSyscall(unix.SYS_IOCTL, uintptr(fd), KVM_CHECK_EXTENSION, _KVM_CAP_ARM_VM_IPA_SIZE) + if errno == 0 && maxIPA > 0 { + if maxIPA < 40 { + ring0.PhysicalAddressBits = uintptr(maxIPA) + } else { + ring0.PhysicalAddressBits = 40 + } + } + ring0.Init() + initFaultBlocks() physicalInit() // Explicitly configure address space for 48-bit VA. diff --git a/pkg/sentry/platform/kvm/machine.go b/pkg/sentry/platform/kvm/machine.go index 3f2d770955..5e20421f1b 100644 --- a/pkg/sentry/platform/kvm/machine.go +++ b/pkg/sentry/platform/kvm/machine.go @@ -331,13 +331,9 @@ func newMachine(vm int, config *Config) (*machine, error) { // mmap isn't called frequently, it seems better than the memory and // startup time overhead introduced by mapping the entire address // space. - mapEntireAddressSpace := forceMappingEntireAddressSpace || - runtime.GOARCH != "amd64" + mapEntireAddressSpace := forceMappingEntireAddressSpace || mapEntireAddressSpaceDefault if mapEntireAddressSpace { - // Increase faultBlockSize to be sure that we will not reach the limit. - // faultBlockSize has to equal or less than KVM_MEM_MAX_NR_PAGES. - faultBlockSize = uintptr(1) << 42 - faultBlockMask = ^uintptr(faultBlockSize - 1) + archOverrideFaultBlocks() } else { // Install seccomp rules to trap runtime mmap system calls. They will // be handled by seccompMmapHandler. diff --git a/pkg/sentry/platform/kvm/machine_amd64.go b/pkg/sentry/platform/kvm/machine_amd64.go index 830a543a44..5f54bd09da 100644 --- a/pkg/sentry/platform/kvm/machine_amd64.go +++ b/pkg/sentry/platform/kvm/machine_amd64.go @@ -579,3 +579,17 @@ func (m *machine) getMaxVCPU() { func archPhysicalRegions(physicalRegions []physicalRegion) []physicalRegion { return physicalRegions } + +const mapEntireAddressSpaceDefault = false + +func initFaultBlocks() { + faultBlockSize = uintptr(8 << 30) // 8 GiB + faultBlockMask = ^uintptr(faultBlockSize - 1) +} + +func archOverrideFaultBlocks() { + // Increase faultBlockSize to be sure that we will not reach the limit. + // faultBlockSize has to equal or less than KVM_MEM_MAX_NR_PAGES. + faultBlockSize = uintptr(1) << 42 + faultBlockMask = ^uintptr(faultBlockSize - 1) +} diff --git a/pkg/sentry/platform/kvm/machine_arm64.go b/pkg/sentry/platform/kvm/machine_arm64.go index b9b16252a7..f24a2ab3a6 100644 --- a/pkg/sentry/platform/kvm/machine_arm64.go +++ b/pkg/sentry/platform/kvm/machine_arm64.go @@ -205,3 +205,16 @@ func (m *machine) getMaxVCPU() { } } } + +const mapEntireAddressSpaceDefault = true + +func initFaultBlocks() { + if ring0.PhysicalAddressBits <= 39 { + faultBlockSize = uintptr(1 << 30) // 1 GiB + } else { + faultBlockSize = uintptr(8 << 30) // 8 GiB + } + faultBlockMask = ^uintptr(faultBlockSize - 1) +} + +func archOverrideFaultBlocks() {}