diff --git a/pkg/sentry/platform/kvm/BUILD b/pkg/sentry/platform/kvm/BUILD index 35b0c8120..6c7c461e5 100644 --- a/pkg/sentry/platform/kvm/BUILD +++ b/pkg/sentry/platform/kvm/BUILD @@ -60,6 +60,7 @@ go_library( "machine_amd64_unsafe.go", "machine_arm64.go", "machine_arm64_unsafe.go", + "machine_cgo.go", "machine_unsafe.go", "physical_map.go", "physical_map_amd64.go", @@ -109,9 +110,6 @@ go_test( library = ":kvm", # FIXME(gvisor.dev/issue/3374): Not working with all build systems. nogo = False, - # cgo has to be disabled. We have seen libc that blocks all signals and - # calls mmap from pthread_create, but we use SIGSYS to trap mmap system - # calls. pure = True, tags = [ "manual", @@ -134,3 +132,37 @@ go_test( "@org_golang_x_sys//unix:go_default_library", ], ) + +go_test( + name = "kvm_cgo_test", + srcs = [ + "kvm_amd64_test.go", + "kvm_amd64_test.s", + "kvm_arm64_test.go", + "kvm_safecopy_test.go", + "kvm_test.go", + "virtual_map_test.go", + ], + library = ":kvm", + # FIXME(gvisor.dev/issue/3374): Not working with all build systems. + nogo = False, + tags = [ + "nogotsan", + "requires-kvm", + ], + deps = [ + "//pkg/abi/linux", + "//pkg/cpuid", + "//pkg/hostarch", + "//pkg/memutil", + "//pkg/ring0", + "//pkg/ring0/pagetables", + "//pkg/safecopy", + "//pkg/sentry/arch", + "//pkg/sentry/arch/fpu", + "//pkg/sentry/platform", + "//pkg/sentry/platform/kvm/testutil", + "//pkg/sentry/time", + "@org_golang_x_sys//unix:go_default_library", + ], +) diff --git a/pkg/sentry/platform/kvm/machine.go b/pkg/sentry/platform/kvm/machine.go index 3fefbadfb..4cf52ed92 100644 --- a/pkg/sentry/platform/kvm/machine.go +++ b/pkg/sentry/platform/kvm/machine.go @@ -25,7 +25,6 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/atomicbitops" "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/hostos" "gvisor.dev/gvisor/pkg/hostsyscall" "gvisor.dev/gvisor/pkg/hosttid" "gvisor.dev/gvisor/pkg/log" @@ -263,14 +262,9 @@ func (m *machine) createVCPU(id int) *vCPU { return c // Done. } -// AllowMappingEntireAddressSpace enables mapping the entire process address +// forceMappingEntireAddressSpace forces mapping the entire process address // space to the VM. -// -// On x86_64, kernels before 6.9 with enabled -// CONFIG_KVM_EXTERNAL_WRITE_TRACKING didn't allow us to map the entire -// process address space to the VM, because the memory tracking was always -// enabled and it has a significant memory overhead. -var AllowMappingEntireAddressSpace bool +var forceMappingEntireAddressSpace = false // newMachine returns a new VM context. func newMachine(vm int) (*machine, error) { @@ -307,16 +301,22 @@ func newMachine(vm int) (*machine, error) { m.upperSharedPageTables.MarkReadOnlyShared() m.kernel.PageTables = pagetables.NewWithUpper(newAllocator(), m.upperSharedPageTables, ring0.KernelStartAddress) - // Before the 6.9 kernel we couldn't map the entire sentry - // address space into VM, because there were a two-byte overhead - // per page in the kernel. This issue was fixed by a364c014a2c1 - // ("kvm/x86: allocate the write-tracking metadata on-demand"). - kernelVersion, err := hostos.KernelVersion() - if err != nil { - return nil, err - } - mapEntireAddressSpace := AllowMappingEntireAddressSpace || - runtime.GOARCH != "amd64" || kernelVersion.AtLeast(6, 9) + // On x86_64, we prefer not to map the entire sentry address space into + // the VM due to memory overhead. It is about 3MB for a 40-bit address + // space and about 250MB for 46-bit address spaces (modern CPUs). + // + // Before version 6.9, the memory overhead was two bytes per page. + // This issue was fixed by commit a364c014a2c1 ("kvm/x86: allocate the + // write-tracking metadata on-demand"). + // + // If the entire address space isn't mapped into the VM, we need to + // trap mmap system calls and map sentry memory regions on demand. This + // introduces some overhead for mmap system calls, but considering that + // 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" 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. diff --git a/pkg/sentry/platform/kvm/machine_cgo.go b/pkg/sentry/platform/kvm/machine_cgo.go new file mode 100644 index 000000000..f2f79ac14 --- /dev/null +++ b/pkg/sentry/platform/kvm/machine_cgo.go @@ -0,0 +1,31 @@ +// Copyright 2024 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build cgo && amd64 +// +build cgo,amd64 + +package kvm + +import ( + "gvisor.dev/gvisor/pkg/ring0" +) + +func init() { + // libc calls mmap with blocked signals. In this case, the mmap system call + // can't be trapped with seccomp. + forceMappingEntireAddressSpace = true + // Limit the physical address space size to control the memory + // overhead. It is about 3MB for 40 bits address space. + ring0.PhysicalAddressBits = 40 +}