From babeb34ee8ed195e0010b3b27235b789fe58bb96 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 18 Oct 2024 14:06:21 -0700 Subject: [PATCH] platform/kvm: don't map the entire sentry address space to VM 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). 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. * native mmap: BenchmarkHostMMap-12 266972 4297 ns/op * mmpa with the seccomp trap: BenchmarkHostMMap-12 174373 6855 ns/op PiperOrigin-RevId: 687418707 --- pkg/sentry/platform/kvm/BUILD | 38 ++++++++++++++++++++++++-- pkg/sentry/platform/kvm/machine.go | 36 ++++++++++++------------ pkg/sentry/platform/kvm/machine_cgo.go | 31 +++++++++++++++++++++ 3 files changed, 84 insertions(+), 21 deletions(-) create mode 100644 pkg/sentry/platform/kvm/machine_cgo.go 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 +}