mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
support using KVM_MEM_READONLY for arm64 regions
On Arm platform, "setMemoryRegion" has extra permission checks.
In virt/kvm/arm/mmu.c: kvm_arch_prepare_memory_region()
....
if (writable && !(vma->vm_flags & VM_WRITE)) {
ret = -EPERM;
break;
}
....
So, for Arm platform, the "flags" for kvm_memory_region is required.
And on x86 platform, the "flags" can be always set as '0'.
Signed-off-by: Bin Lu <bin.lu@arm.com>
COPYBARA_INTEGRATE_REVIEW=https://github.com/google/gvisor/pull/810 from lubinszARM:pr_setregion 8c99b19cfb0c859c6630a1cfff951db65fcf87ac
PiperOrigin-RevId: 277602603
This commit is contained in:
@@ -23,6 +23,7 @@ go_library(
|
||||
"machine.go",
|
||||
"machine_amd64.go",
|
||||
"machine_amd64_unsafe.go",
|
||||
"machine_arm64.go",
|
||||
"machine_unsafe.go",
|
||||
"physical_map.go",
|
||||
"virtual_map.go",
|
||||
|
||||
@@ -127,7 +127,7 @@ func (as *addressSpace) mapHost(addr usermem.Addr, m hostMapEntry, at usermem.Ac
|
||||
// not have physical mappings, the KVM module may inject
|
||||
// spurious exceptions when emulation fails (i.e. it tries to
|
||||
// emulate because the RIP is pointed at those pages).
|
||||
as.machine.mapPhysical(physical, length)
|
||||
as.machine.mapPhysical(physical, length, physicalRegions, _KVM_MEM_FLAGS_NONE)
|
||||
|
||||
// Install the page table mappings. Note that the ordering is
|
||||
// important; if the pagetable mappings were installed before
|
||||
|
||||
@@ -54,7 +54,7 @@ func (a allocator) PhysicalFor(ptes *pagetables.PTEs) uintptr {
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) LookupPTEs(physical uintptr) *pagetables.PTEs {
|
||||
virtualStart, physicalStart, _, ok := calculateBluepillFault(physical)
|
||||
virtualStart, physicalStart, _, ok := calculateBluepillFault(physical, physicalRegions)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("LookupPTEs failed for 0x%x", physical))
|
||||
}
|
||||
|
||||
@@ -46,9 +46,9 @@ func yield() {
|
||||
// calculateBluepillFault calculates the fault address range.
|
||||
//
|
||||
//go:nosplit
|
||||
func calculateBluepillFault(physical uintptr) (virtualStart, physicalStart, length uintptr, ok bool) {
|
||||
func calculateBluepillFault(physical uintptr, phyRegions []physicalRegion) (virtualStart, physicalStart, length uintptr, ok bool) {
|
||||
alignedPhysical := physical &^ uintptr(usermem.PageSize-1)
|
||||
for _, pr := range physicalRegions {
|
||||
for _, pr := range phyRegions {
|
||||
end := pr.physical + pr.length
|
||||
if physical < pr.physical || physical >= end {
|
||||
continue
|
||||
@@ -77,12 +77,12 @@ func calculateBluepillFault(physical uintptr) (virtualStart, physicalStart, leng
|
||||
// The corresponding virtual address is returned. This may throw on error.
|
||||
//
|
||||
//go:nosplit
|
||||
func handleBluepillFault(m *machine, physical uintptr) (uintptr, bool) {
|
||||
func handleBluepillFault(m *machine, physical uintptr, phyRegions []physicalRegion, flags uint32) (uintptr, bool) {
|
||||
// Paging fault: we need to map the underlying physical pages for this
|
||||
// fault. This all has to be done in this function because we're in a
|
||||
// signal handler context. (We can't call any functions that might
|
||||
// split the stack.)
|
||||
virtualStart, physicalStart, length, ok := calculateBluepillFault(physical)
|
||||
virtualStart, physicalStart, length, ok := calculateBluepillFault(physical, phyRegions)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func handleBluepillFault(m *machine, physical uintptr) (uintptr, bool) {
|
||||
yield() // Race with another call.
|
||||
slot = atomic.SwapUint32(&m.nextSlot, ^uint32(0))
|
||||
}
|
||||
errno := m.setMemoryRegion(int(slot), physicalStart, length, virtualStart)
|
||||
errno := m.setMemoryRegion(int(slot), physicalStart, length, virtualStart, flags)
|
||||
if errno == 0 {
|
||||
// Successfully added region; we can increment nextSlot and
|
||||
// allow another set to proceed here.
|
||||
|
||||
@@ -162,7 +162,7 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
|
||||
// For MMIO, the physical address is the first data item.
|
||||
physical := uintptr(c.runData.data[0])
|
||||
virtual, ok := handleBluepillFault(c.machine, physical)
|
||||
virtual, ok := handleBluepillFault(c.machine, physical, physicalRegions, _KVM_MEM_FLAGS_NONE)
|
||||
if !ok {
|
||||
c.die(bluepillArchContext(context), "invalid physical address")
|
||||
return
|
||||
|
||||
@@ -62,3 +62,10 @@ const (
|
||||
_KVM_NR_INTERRUPTS = 0x100
|
||||
_KVM_NR_CPUID_ENTRIES = 0x100
|
||||
)
|
||||
|
||||
// KVM kvm_memory_region::flags.
|
||||
const (
|
||||
_KVM_MEM_LOG_DIRTY_PAGES = uint32(1) << 0
|
||||
_KVM_MEM_READONLY = uint32(1) << 1
|
||||
_KVM_MEM_FLAGS_NONE = 0
|
||||
)
|
||||
|
||||
@@ -215,6 +215,17 @@ func newMachine(vm int) (*machine, error) {
|
||||
return true // Keep iterating.
|
||||
})
|
||||
|
||||
var physicalRegionsReadOnly []physicalRegion
|
||||
var physicalRegionsAvailable []physicalRegion
|
||||
|
||||
physicalRegionsReadOnly = rdonlyRegionsForSetMem()
|
||||
physicalRegionsAvailable = availableRegionsForSetMem()
|
||||
|
||||
// Map all read-only regions.
|
||||
for _, r := range physicalRegionsReadOnly {
|
||||
m.mapPhysical(r.physical, r.length, physicalRegionsReadOnly, _KVM_MEM_READONLY)
|
||||
}
|
||||
|
||||
// Ensure that the currently mapped virtual regions are actually
|
||||
// available in the VM. Note that this doesn't guarantee no future
|
||||
// faults, however it should guarantee that everything is available to
|
||||
@@ -223,6 +234,13 @@ func newMachine(vm int) (*machine, error) {
|
||||
if excludeVirtualRegion(vr) {
|
||||
return // skip region.
|
||||
}
|
||||
|
||||
for _, r := range physicalRegionsReadOnly {
|
||||
if vr.virtual == r.virtual {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for virtual := vr.virtual; virtual < vr.virtual+vr.length; {
|
||||
physical, length, ok := translateToPhysical(virtual)
|
||||
if !ok {
|
||||
@@ -236,7 +254,7 @@ func newMachine(vm int) (*machine, error) {
|
||||
}
|
||||
|
||||
// Ensure the physical range is mapped.
|
||||
m.mapPhysical(physical, length)
|
||||
m.mapPhysical(physical, length, physicalRegionsAvailable, _KVM_MEM_FLAGS_NONE)
|
||||
virtual += length
|
||||
}
|
||||
})
|
||||
@@ -256,9 +274,9 @@ func newMachine(vm int) (*machine, error) {
|
||||
// not available. This attempts to be efficient for calls in the hot path.
|
||||
//
|
||||
// This panics on error.
|
||||
func (m *machine) mapPhysical(physical, length uintptr) {
|
||||
func (m *machine) mapPhysical(physical, length uintptr, phyRegions []physicalRegion, flags uint32) {
|
||||
for end := physical + length; physical < end; {
|
||||
_, physicalStart, length, ok := calculateBluepillFault(physical)
|
||||
_, physicalStart, length, ok := calculateBluepillFault(physical, phyRegions)
|
||||
if !ok {
|
||||
// Should never happen.
|
||||
panic("mapPhysical on unknown physical address")
|
||||
@@ -266,7 +284,7 @@ func (m *machine) mapPhysical(physical, length uintptr) {
|
||||
|
||||
if _, ok := m.mappingCache.LoadOrStore(physicalStart, true); !ok {
|
||||
// Not present in the cache; requires setting the slot.
|
||||
if _, ok := handleBluepillFault(m, physical); !ok {
|
||||
if _, ok := handleBluepillFault(m, physical, phyRegions, flags); !ok {
|
||||
panic("handleBluepillFault failed")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -355,3 +355,13 @@ func (m *machine) retryInGuest(fn func()) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// On x86 platform, the flags for "setMemoryRegion" can always be set as 0.
|
||||
// There is no need to return read-only physicalRegions.
|
||||
func rdonlyRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func availableRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
return physicalRegions
|
||||
}
|
||||
|
||||
@@ -26,30 +26,6 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/time"
|
||||
)
|
||||
|
||||
// setMemoryRegion initializes a region.
|
||||
//
|
||||
// This may be called from bluepillHandler, and therefore returns an errno
|
||||
// directly (instead of wrapping in an error) to avoid allocations.
|
||||
//
|
||||
//go:nosplit
|
||||
func (m *machine) setMemoryRegion(slot int, physical, length, virtual uintptr) syscall.Errno {
|
||||
userRegion := userMemoryRegion{
|
||||
slot: uint32(slot),
|
||||
flags: 0,
|
||||
guestPhysAddr: uint64(physical),
|
||||
memorySize: uint64(length),
|
||||
userspaceAddr: uint64(virtual),
|
||||
}
|
||||
|
||||
// Set the region.
|
||||
_, _, errno := syscall.RawSyscall(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(m.fd),
|
||||
_KVM_SET_USER_MEMORY_REGION,
|
||||
uintptr(unsafe.Pointer(&userRegion)))
|
||||
return errno
|
||||
}
|
||||
|
||||
// loadSegments copies the current segments.
|
||||
//
|
||||
// This may be called from within the signal context and throws on error.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2019 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.
|
||||
|
||||
package kvm
|
||||
|
||||
// Get all read-only physicalRegions.
|
||||
func rdonlyRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
var rdonlyRegions []region
|
||||
|
||||
applyVirtualRegions(func(vr virtualRegion) {
|
||||
if excludeVirtualRegion(vr) {
|
||||
return
|
||||
}
|
||||
|
||||
if !vr.accessType.Write && vr.accessType.Read {
|
||||
rdonlyRegions = append(rdonlyRegions, vr.region)
|
||||
}
|
||||
})
|
||||
|
||||
for _, r := range rdonlyRegions {
|
||||
physical, _, ok := translateToPhysical(r.virtual)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
phyRegions = append(phyRegions, physicalRegion{
|
||||
region: region{
|
||||
virtual: r.virtual,
|
||||
length: r.length,
|
||||
},
|
||||
physical: physical,
|
||||
})
|
||||
}
|
||||
|
||||
return phyRegions
|
||||
}
|
||||
|
||||
// Get all available physicalRegions.
|
||||
func availableRegionsForSetMem() (phyRegions []physicalRegion) {
|
||||
var excludeRegions []region
|
||||
applyVirtualRegions(func(vr virtualRegion) {
|
||||
if !vr.accessType.Write {
|
||||
excludeRegions = append(excludeRegions, vr.region)
|
||||
}
|
||||
})
|
||||
|
||||
phyRegions = computePhysicalRegions(excludeRegions)
|
||||
|
||||
return phyRegions
|
||||
}
|
||||
@@ -35,6 +35,30 @@ func entersyscall()
|
||||
//go:linkname exitsyscall runtime.exitsyscall
|
||||
func exitsyscall()
|
||||
|
||||
// setMemoryRegion initializes a region.
|
||||
//
|
||||
// This may be called from bluepillHandler, and therefore returns an errno
|
||||
// directly (instead of wrapping in an error) to avoid allocations.
|
||||
//
|
||||
//go:nosplit
|
||||
func (m *machine) setMemoryRegion(slot int, physical, length, virtual uintptr, flags uint32) syscall.Errno {
|
||||
userRegion := userMemoryRegion{
|
||||
slot: uint32(slot),
|
||||
flags: uint32(flags),
|
||||
guestPhysAddr: uint64(physical),
|
||||
memorySize: uint64(length),
|
||||
userspaceAddr: uint64(virtual),
|
||||
}
|
||||
|
||||
// Set the region.
|
||||
_, _, errno := syscall.RawSyscall(
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(m.fd),
|
||||
_KVM_SET_USER_MEMORY_REGION,
|
||||
uintptr(unsafe.Pointer(&userRegion)))
|
||||
return errno
|
||||
}
|
||||
|
||||
// mapRunData maps the vCPU run data.
|
||||
func mapRunData(fd int) (*runData, error) {
|
||||
r, _, errno := syscall.RawSyscall6(
|
||||
|
||||
Reference in New Issue
Block a user