mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add +checkescape annotations to kvm/ring0.
This analysis also catches a potential bug, which is a split on mapPhysical. This would have led to potential guest-exit during Mapping (although this would have been handled by the now-unecessary retryInGuest loop). PiperOrigin-RevId: 315025106
This commit is contained in:
committed by
gVisor bot
parent
8d8dce418f
commit
527d08f6af
@@ -6,8 +6,8 @@ go_library(
|
||||
name = "kvm",
|
||||
srcs = [
|
||||
"address_space.go",
|
||||
"allocator.go",
|
||||
"bluepill.go",
|
||||
"bluepill_allocator.go",
|
||||
"bluepill_amd64.go",
|
||||
"bluepill_amd64.s",
|
||||
"bluepill_amd64_unsafe.go",
|
||||
|
||||
@@ -26,16 +26,15 @@ import (
|
||||
|
||||
// dirtySet tracks vCPUs for invalidation.
|
||||
type dirtySet struct {
|
||||
vCPUs []uint64
|
||||
vCPUMasks []uint64
|
||||
}
|
||||
|
||||
// forEach iterates over all CPUs in the dirty set.
|
||||
//
|
||||
//go:nosplit
|
||||
func (ds *dirtySet) forEach(m *machine, fn func(c *vCPU)) {
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
|
||||
for index := range ds.vCPUs {
|
||||
mask := atomic.SwapUint64(&ds.vCPUs[index], 0)
|
||||
for index := range ds.vCPUMasks {
|
||||
mask := atomic.SwapUint64(&ds.vCPUMasks[index], 0)
|
||||
if mask != 0 {
|
||||
for bit := 0; bit < 64; bit++ {
|
||||
if mask&(1<<uint64(bit)) == 0 {
|
||||
@@ -54,7 +53,7 @@ func (ds *dirtySet) mark(c *vCPU) bool {
|
||||
index := uint64(c.id) / 64
|
||||
bit := uint64(1) << uint(c.id%64)
|
||||
|
||||
oldValue := atomic.LoadUint64(&ds.vCPUs[index])
|
||||
oldValue := atomic.LoadUint64(&ds.vCPUMasks[index])
|
||||
if oldValue&bit != 0 {
|
||||
return false // Not clean.
|
||||
}
|
||||
@@ -62,7 +61,7 @@ func (ds *dirtySet) mark(c *vCPU) bool {
|
||||
// Set the bit unilaterally, and ensure that a flush takes place. Note
|
||||
// that it's possible for races to occur here, but since the flush is
|
||||
// taking place long after these lines there's no race in practice.
|
||||
atomicbitops.OrUint64(&ds.vCPUs[index], bit)
|
||||
atomicbitops.OrUint64(&ds.vCPUMasks[index], bit)
|
||||
return true // Previously clean.
|
||||
}
|
||||
|
||||
@@ -113,7 +112,12 @@ type hostMapEntry struct {
|
||||
length uintptr
|
||||
}
|
||||
|
||||
func (as *addressSpace) mapHost(addr usermem.Addr, m hostMapEntry, at usermem.AccessType) (inv bool) {
|
||||
// mapLocked maps the given host entry.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (as *addressSpace) mapLocked(addr usermem.Addr, m hostMapEntry, at usermem.AccessType) (inv bool) {
|
||||
for m.length > 0 {
|
||||
physical, length, ok := translateToPhysical(m.addr)
|
||||
if !ok {
|
||||
@@ -133,18 +137,10 @@ func (as *addressSpace) mapHost(addr usermem.Addr, m hostMapEntry, at usermem.Ac
|
||||
// important; if the pagetable mappings were installed before
|
||||
// ensuring the physical pages were available, then some other
|
||||
// thread could theoretically access them.
|
||||
//
|
||||
// Due to the way KVM's shadow paging implementation works,
|
||||
// modifications to the page tables while in host mode may not
|
||||
// be trapped, leading to the shadow pages being out of sync.
|
||||
// Therefore, we need to ensure that we are in guest mode for
|
||||
// page table modifications. See the call to bluepill, below.
|
||||
as.machine.retryInGuest(func() {
|
||||
inv = as.pageTables.Map(addr, length, pagetables.MapOpts{
|
||||
AccessType: at,
|
||||
User: true,
|
||||
}, physical) || inv
|
||||
})
|
||||
inv = as.pageTables.Map(addr, length, pagetables.MapOpts{
|
||||
AccessType: at,
|
||||
User: true,
|
||||
}, physical) || inv
|
||||
m.addr += length
|
||||
m.length -= length
|
||||
addr += usermem.Addr(length)
|
||||
@@ -176,6 +172,10 @@ func (as *addressSpace) MapFile(addr usermem.Addr, f platform.File, fr platform.
|
||||
return err
|
||||
}
|
||||
|
||||
// See block in mapLocked.
|
||||
as.pageTables.Allocator.(*allocator).cpu = as.machine.Get()
|
||||
defer as.machine.Put(as.pageTables.Allocator.(*allocator).cpu)
|
||||
|
||||
// Map the mappings in the sentry's address space (guest physical memory)
|
||||
// into the application's address space (guest virtual memory).
|
||||
inv := false
|
||||
@@ -190,7 +190,12 @@ func (as *addressSpace) MapFile(addr usermem.Addr, f platform.File, fr platform.
|
||||
_ = s[i] // Touch to commit.
|
||||
}
|
||||
}
|
||||
prev := as.mapHost(addr, hostMapEntry{
|
||||
|
||||
// See bluepill_allocator.go.
|
||||
bluepill(as.pageTables.Allocator.(*allocator).cpu)
|
||||
|
||||
// Perform the mapping.
|
||||
prev := as.mapLocked(addr, hostMapEntry{
|
||||
addr: b.Addr(),
|
||||
length: uintptr(b.Len()),
|
||||
}, at)
|
||||
@@ -204,17 +209,27 @@ func (as *addressSpace) MapFile(addr usermem.Addr, f platform.File, fr platform.
|
||||
return nil
|
||||
}
|
||||
|
||||
// unmapLocked is an escape-checked wrapped around Unmap.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (as *addressSpace) unmapLocked(addr usermem.Addr, length uint64) bool {
|
||||
return as.pageTables.Unmap(addr, uintptr(length))
|
||||
}
|
||||
|
||||
// Unmap unmaps the given range by calling pagetables.PageTables.Unmap.
|
||||
func (as *addressSpace) Unmap(addr usermem.Addr, length uint64) {
|
||||
as.mu.Lock()
|
||||
defer as.mu.Unlock()
|
||||
|
||||
// See above re: retryInGuest.
|
||||
var prev bool
|
||||
as.machine.retryInGuest(func() {
|
||||
prev = as.pageTables.Unmap(addr, uintptr(length)) || prev
|
||||
})
|
||||
if prev {
|
||||
// See above & bluepill_allocator.go.
|
||||
as.pageTables.Allocator.(*allocator).cpu = as.machine.Get()
|
||||
defer as.machine.Put(as.pageTables.Allocator.(*allocator).cpu)
|
||||
bluepill(as.pageTables.Allocator.(*allocator).cpu)
|
||||
|
||||
if prev := as.unmapLocked(addr, length); prev {
|
||||
// Invalidate all active vCPUs.
|
||||
as.invalidate()
|
||||
|
||||
// Recycle any freed intermediate pages.
|
||||
@@ -227,7 +242,7 @@ func (as *addressSpace) Release() {
|
||||
as.Unmap(0, ^uint64(0))
|
||||
|
||||
// Free all pages from the allocator.
|
||||
as.pageTables.Allocator.(allocator).base.Drain()
|
||||
as.pageTables.Allocator.(*allocator).base.Drain()
|
||||
|
||||
// Drop all cached machine references.
|
||||
as.machine.dropPageTables(as.pageTables)
|
||||
|
||||
+38
-14
@@ -21,56 +21,80 @@ import (
|
||||
)
|
||||
|
||||
type allocator struct {
|
||||
base *pagetables.RuntimeAllocator
|
||||
base pagetables.RuntimeAllocator
|
||||
|
||||
// cpu must be set prior to any pagetable operation.
|
||||
//
|
||||
// Due to the way KVM's shadow paging implementation works,
|
||||
// modifications to the page tables while in host mode may not be
|
||||
// trapped, leading to the shadow pages being out of sync. Therefore,
|
||||
// we need to ensure that we are in guest mode for page table
|
||||
// modifications. See the call to bluepill, below.
|
||||
cpu *vCPU
|
||||
}
|
||||
|
||||
// newAllocator is used to define the allocator.
|
||||
func newAllocator() allocator {
|
||||
return allocator{
|
||||
base: pagetables.NewRuntimeAllocator(),
|
||||
}
|
||||
func newAllocator() *allocator {
|
||||
a := new(allocator)
|
||||
a.base.Init()
|
||||
return a
|
||||
}
|
||||
|
||||
// NewPTEs implements pagetables.Allocator.NewPTEs.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) NewPTEs() *pagetables.PTEs {
|
||||
return a.base.NewPTEs()
|
||||
func (a *allocator) NewPTEs() *pagetables.PTEs {
|
||||
ptes := a.base.NewPTEs() // escapes: bluepill below.
|
||||
if a.cpu != nil {
|
||||
bluepill(a.cpu)
|
||||
}
|
||||
return ptes
|
||||
}
|
||||
|
||||
// PhysicalFor returns the physical address for a set of PTEs.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) PhysicalFor(ptes *pagetables.PTEs) uintptr {
|
||||
func (a *allocator) PhysicalFor(ptes *pagetables.PTEs) uintptr {
|
||||
virtual := a.base.PhysicalFor(ptes)
|
||||
physical, _, ok := translateToPhysical(virtual)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("PhysicalFor failed for %p", ptes))
|
||||
panic(fmt.Sprintf("PhysicalFor failed for %p", ptes)) // escapes: panic.
|
||||
}
|
||||
return physical
|
||||
}
|
||||
|
||||
// LookupPTEs implements pagetables.Allocator.LookupPTEs.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) LookupPTEs(physical uintptr) *pagetables.PTEs {
|
||||
func (a *allocator) LookupPTEs(physical uintptr) *pagetables.PTEs {
|
||||
virtualStart, physicalStart, _, ok := calculateBluepillFault(physical, physicalRegions)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("LookupPTEs failed for 0x%x", physical))
|
||||
panic(fmt.Sprintf("LookupPTEs failed for 0x%x", physical)) // escapes: panic.
|
||||
}
|
||||
return a.base.LookupPTEs(virtualStart + (physical - physicalStart))
|
||||
}
|
||||
|
||||
// FreePTEs implements pagetables.Allocator.FreePTEs.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) FreePTEs(ptes *pagetables.PTEs) {
|
||||
a.base.FreePTEs(ptes)
|
||||
func (a *allocator) FreePTEs(ptes *pagetables.PTEs) {
|
||||
a.base.FreePTEs(ptes) // escapes: bluepill below.
|
||||
if a.cpu != nil {
|
||||
bluepill(a.cpu)
|
||||
}
|
||||
}
|
||||
|
||||
// Recycle implements pagetables.Allocator.Recycle.
|
||||
//
|
||||
//go:nosplit
|
||||
func (a allocator) Recycle() {
|
||||
func (a *allocator) Recycle() {
|
||||
a.base.Recycle()
|
||||
}
|
||||
@@ -63,6 +63,8 @@ func bluepillArchEnter(context *arch.SignalContext64) *vCPU {
|
||||
|
||||
// KernelSyscall handles kernel syscalls.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) KernelSyscall() {
|
||||
regs := c.Registers()
|
||||
@@ -72,13 +74,15 @@ func (c *vCPU) KernelSyscall() {
|
||||
// We only trigger a bluepill entry in the bluepill function, and can
|
||||
// therefore be guaranteed that there is no floating point state to be
|
||||
// loaded on resuming from halt. We only worry about saving on exit.
|
||||
ring0.SaveFloatingPoint((*byte)(c.floatingPointState))
|
||||
ring0.SaveFloatingPoint((*byte)(c.floatingPointState)) // escapes: no.
|
||||
ring0.Halt()
|
||||
ring0.WriteFS(uintptr(regs.Fs_base)) // Reload host segment.
|
||||
ring0.WriteFS(uintptr(regs.Fs_base)) // escapes: no, reload host segment.
|
||||
}
|
||||
|
||||
// KernelException handles kernel exceptions.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) KernelException(vector ring0.Vector) {
|
||||
regs := c.Registers()
|
||||
@@ -89,9 +93,9 @@ func (c *vCPU) KernelException(vector ring0.Vector) {
|
||||
regs.Rip = 0
|
||||
}
|
||||
// See above.
|
||||
ring0.SaveFloatingPoint((*byte)(c.floatingPointState))
|
||||
ring0.SaveFloatingPoint((*byte)(c.floatingPointState)) // escapes: no.
|
||||
ring0.Halt()
|
||||
ring0.WriteFS(uintptr(regs.Fs_base)) // Reload host segment.
|
||||
ring0.WriteFS(uintptr(regs.Fs_base)) // escapes: no; reload host segment.
|
||||
}
|
||||
|
||||
// bluepillArchExit is called during bluepillEnter.
|
||||
|
||||
@@ -66,6 +66,8 @@ func bluepillArchExit(c *vCPU, context *arch.SignalContext64) {
|
||||
|
||||
// KernelSyscall handles kernel syscalls.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) KernelSyscall() {
|
||||
regs := c.Registers()
|
||||
@@ -88,6 +90,8 @@ func (c *vCPU) KernelSyscall() {
|
||||
|
||||
// KernelException handles kernel exceptions.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) KernelException(vector ring0.Vector) {
|
||||
regs := c.Registers()
|
||||
|
||||
@@ -64,6 +64,8 @@ func bluepillArchContext(context unsafe.Pointer) *arch.SignalContext64 {
|
||||
// signal stack. It should only execute raw system calls and functions that are
|
||||
// explicitly marked go:nosplit.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func bluepillHandler(context unsafe.Pointer) {
|
||||
// Sanitize the registers; interrupts must always be disabled.
|
||||
@@ -82,7 +84,8 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
}
|
||||
|
||||
for {
|
||||
switch _, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(c.fd), _KVM_RUN, 0); errno {
|
||||
_, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(c.fd), _KVM_RUN, 0) // escapes: no.
|
||||
switch errno {
|
||||
case 0: // Expected case.
|
||||
case syscall.EINTR:
|
||||
// First, we process whatever pending signal
|
||||
@@ -90,7 +93,7 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
// currently, all signals are masked and the signal
|
||||
// must have been delivered directly to this thread.
|
||||
timeout := syscall.Timespec{}
|
||||
sig, _, errno := syscall.RawSyscall6(
|
||||
sig, _, errno := syscall.RawSyscall6( // escapes: no.
|
||||
syscall.SYS_RT_SIGTIMEDWAIT,
|
||||
uintptr(unsafe.Pointer(&bounceSignalMask)),
|
||||
0, // siginfo.
|
||||
@@ -125,7 +128,7 @@ func bluepillHandler(context unsafe.Pointer) {
|
||||
// MMIO exit we receive EFAULT from the run ioctl. We
|
||||
// always inject an NMI here since we may be in kernel
|
||||
// mode and have interrupts disabled.
|
||||
if _, _, errno := syscall.RawSyscall(
|
||||
if _, _, errno := syscall.RawSyscall( // escapes: no.
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(c.fd),
|
||||
_KVM_NMI, 0); errno != 0 {
|
||||
|
||||
@@ -52,16 +52,19 @@ type machine struct {
|
||||
// available is notified when vCPUs are available.
|
||||
available sync.Cond
|
||||
|
||||
// vCPUs are the machine vCPUs.
|
||||
// vCPUsByTID are the machine vCPUs.
|
||||
//
|
||||
// These are populated dynamically.
|
||||
vCPUs map[uint64]*vCPU
|
||||
vCPUsByTID map[uint64]*vCPU
|
||||
|
||||
// vCPUsByID are the machine vCPUs, can be indexed by the vCPU's ID.
|
||||
vCPUsByID map[int]*vCPU
|
||||
vCPUsByID []*vCPU
|
||||
|
||||
// maxVCPUs is the maximum number of vCPUs supported by the machine.
|
||||
maxVCPUs int
|
||||
|
||||
// nextID is the next vCPU ID.
|
||||
nextID uint32
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -137,9 +140,8 @@ type dieState struct {
|
||||
//
|
||||
// Precondition: mu must be held.
|
||||
func (m *machine) newVCPU() *vCPU {
|
||||
id := len(m.vCPUs)
|
||||
|
||||
// Create the vCPU.
|
||||
id := int(atomic.AddUint32(&m.nextID, 1) - 1)
|
||||
fd, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(m.fd), _KVM_CREATE_VCPU, uintptr(id))
|
||||
if errno != 0 {
|
||||
panic(fmt.Sprintf("error creating new vCPU: %v", errno))
|
||||
@@ -176,11 +178,7 @@ func (m *machine) newVCPU() *vCPU {
|
||||
// newMachine returns a new VM context.
|
||||
func newMachine(vm int) (*machine, error) {
|
||||
// Create the machine.
|
||||
m := &machine{
|
||||
fd: vm,
|
||||
vCPUs: make(map[uint64]*vCPU),
|
||||
vCPUsByID: make(map[int]*vCPU),
|
||||
}
|
||||
m := &machine{fd: vm}
|
||||
m.available.L = &m.mu
|
||||
m.kernel.Init(ring0.KernelOpts{
|
||||
PageTables: pagetables.New(newAllocator()),
|
||||
@@ -194,6 +192,10 @@ func newMachine(vm int) (*machine, error) {
|
||||
}
|
||||
log.Debugf("The maximum number of vCPUs is %d.", m.maxVCPUs)
|
||||
|
||||
// Create the vCPUs map/slices.
|
||||
m.vCPUsByTID = make(map[uint64]*vCPU)
|
||||
m.vCPUsByID = make([]*vCPU, m.maxVCPUs)
|
||||
|
||||
// Apply the physical mappings. Note that these mappings may point to
|
||||
// guest physical addresses that are not actually available. These
|
||||
// physical pages are mapped on demand, see kernel_unsafe.go.
|
||||
@@ -274,6 +276,8 @@ func newMachine(vm int) (*machine, error) {
|
||||
// not available. This attempts to be efficient for calls in the hot path.
|
||||
//
|
||||
// This panics on error.
|
||||
//
|
||||
//go:nosplit
|
||||
func (m *machine) mapPhysical(physical, length uintptr, phyRegions []physicalRegion, flags uint32) {
|
||||
for end := physical + length; physical < end; {
|
||||
_, physicalStart, length, ok := calculateBluepillFault(physical, phyRegions)
|
||||
@@ -304,7 +308,11 @@ func (m *machine) Destroy() {
|
||||
runtime.SetFinalizer(m, nil)
|
||||
|
||||
// Destroy vCPUs.
|
||||
for _, c := range m.vCPUs {
|
||||
for _, c := range m.vCPUsByID {
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the vCPU is not still running in guest mode. This is
|
||||
// possible iff teardown has been done by other threads, and
|
||||
// somehow a single thread has not executed any system calls.
|
||||
@@ -337,7 +345,7 @@ func (m *machine) Get() *vCPU {
|
||||
tid := procid.Current()
|
||||
|
||||
// Check for an exact match.
|
||||
if c := m.vCPUs[tid]; c != nil {
|
||||
if c := m.vCPUsByTID[tid]; c != nil {
|
||||
c.lock()
|
||||
m.mu.RUnlock()
|
||||
return c
|
||||
@@ -356,7 +364,7 @@ func (m *machine) Get() *vCPU {
|
||||
tid = procid.Current()
|
||||
|
||||
// Recheck for an exact match.
|
||||
if c := m.vCPUs[tid]; c != nil {
|
||||
if c := m.vCPUsByTID[tid]; c != nil {
|
||||
c.lock()
|
||||
m.mu.Unlock()
|
||||
return c
|
||||
@@ -364,10 +372,10 @@ func (m *machine) Get() *vCPU {
|
||||
|
||||
for {
|
||||
// Scan for an available vCPU.
|
||||
for origTID, c := range m.vCPUs {
|
||||
for origTID, c := range m.vCPUsByTID {
|
||||
if atomic.CompareAndSwapUint32(&c.state, vCPUReady, vCPUUser) {
|
||||
delete(m.vCPUs, origTID)
|
||||
m.vCPUs[tid] = c
|
||||
delete(m.vCPUsByTID, origTID)
|
||||
m.vCPUsByTID[tid] = c
|
||||
m.mu.Unlock()
|
||||
c.loadSegments(tid)
|
||||
return c
|
||||
@@ -375,17 +383,17 @@ func (m *machine) Get() *vCPU {
|
||||
}
|
||||
|
||||
// Create a new vCPU (maybe).
|
||||
if len(m.vCPUs) < m.maxVCPUs {
|
||||
if int(m.nextID) < m.maxVCPUs {
|
||||
c := m.newVCPU()
|
||||
c.lock()
|
||||
m.vCPUs[tid] = c
|
||||
m.vCPUsByTID[tid] = c
|
||||
m.mu.Unlock()
|
||||
c.loadSegments(tid)
|
||||
return c
|
||||
}
|
||||
|
||||
// Scan for something not in user mode.
|
||||
for origTID, c := range m.vCPUs {
|
||||
for origTID, c := range m.vCPUsByTID {
|
||||
if !atomic.CompareAndSwapUint32(&c.state, vCPUGuest, vCPUGuest|vCPUWaiter) {
|
||||
continue
|
||||
}
|
||||
@@ -403,8 +411,8 @@ func (m *machine) Get() *vCPU {
|
||||
}
|
||||
|
||||
// Steal the vCPU.
|
||||
delete(m.vCPUs, origTID)
|
||||
m.vCPUs[tid] = c
|
||||
delete(m.vCPUsByTID, origTID)
|
||||
m.vCPUsByTID[tid] = c
|
||||
m.mu.Unlock()
|
||||
c.loadSegments(tid)
|
||||
return c
|
||||
@@ -431,7 +439,7 @@ func (m *machine) Put(c *vCPU) {
|
||||
// newDirtySet returns a new dirty set.
|
||||
func (m *machine) newDirtySet() *dirtySet {
|
||||
return &dirtySet{
|
||||
vCPUs: make([]uint64, (m.maxVCPUs+63)/64, (m.maxVCPUs+63)/64),
|
||||
vCPUMasks: make([]uint64, (m.maxVCPUs+63)/64, (m.maxVCPUs+63)/64),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,10 @@ func (m *machine) initArchState() error {
|
||||
recover()
|
||||
debug.SetPanicOnFault(old)
|
||||
}()
|
||||
m.retryInGuest(func() {
|
||||
ring0.SetCPUIDFaulting(true)
|
||||
})
|
||||
c := m.Get()
|
||||
defer m.Put(c)
|
||||
bluepill(c)
|
||||
ring0.SetCPUIDFaulting(true)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -89,8 +90,8 @@ func (m *machine) dropPageTables(pt *pagetables.PageTables) {
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// Clear from all PCIDs.
|
||||
for _, c := range m.vCPUs {
|
||||
if c.PCIDs != nil {
|
||||
for _, c := range m.vCPUsByID {
|
||||
if c != nil && c.PCIDs != nil {
|
||||
c.PCIDs.Drop(pt)
|
||||
}
|
||||
}
|
||||
@@ -335,29 +336,6 @@ func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts, info *arch.SignalInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// retryInGuest runs the given function in guest mode.
|
||||
//
|
||||
// If the function does not complete in guest mode (due to execution of a
|
||||
// system call due to a GC stall, for example), then it will be retried. The
|
||||
// given function must be idempotent as a result of the retry mechanism.
|
||||
func (m *machine) retryInGuest(fn func()) {
|
||||
c := m.Get()
|
||||
defer m.Put(c)
|
||||
for {
|
||||
c.ClearErrorCode() // See below.
|
||||
bluepill(c) // Force guest mode.
|
||||
fn() // Execute the given function.
|
||||
_, user := c.ErrorCode()
|
||||
if user {
|
||||
// If user is set, then we haven't bailed back to host
|
||||
// mode via a kernel exception or system call. We
|
||||
// consider the full function to have executed in guest
|
||||
// mode and we can return.
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -154,7 +154,7 @@ func (c *vCPU) setUserRegisters(uregs *userRegs) error {
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) getUserRegisters(uregs *userRegs) syscall.Errno {
|
||||
if _, _, errno := syscall.RawSyscall(
|
||||
if _, _, errno := syscall.RawSyscall( // escapes: no.
|
||||
syscall.SYS_IOCTL,
|
||||
uintptr(c.fd),
|
||||
_KVM_GET_REGS,
|
||||
|
||||
@@ -115,7 +115,7 @@ func (a *atomicAddressSpace) get() *addressSpace {
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *vCPU) notify() {
|
||||
_, _, errno := syscall.RawSyscall6(
|
||||
_, _, errno := syscall.RawSyscall6( // escapes: no.
|
||||
syscall.SYS_FUTEX,
|
||||
uintptr(unsafe.Pointer(&c.state)),
|
||||
linux.FUTEX_WAKE|linux.FUTEX_PRIVATE_FLAG,
|
||||
|
||||
@@ -31,23 +31,39 @@ type defaultHooks struct{}
|
||||
|
||||
// KernelSyscall implements Hooks.KernelSyscall.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (defaultHooks) KernelSyscall() { Halt() }
|
||||
func (defaultHooks) KernelSyscall() {
|
||||
Halt()
|
||||
}
|
||||
|
||||
// KernelException implements Hooks.KernelException.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (defaultHooks) KernelException(Vector) { Halt() }
|
||||
func (defaultHooks) KernelException(Vector) {
|
||||
Halt()
|
||||
}
|
||||
|
||||
// kernelSyscall is a trampoline.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func kernelSyscall(c *CPU) { c.hooks.KernelSyscall() }
|
||||
func kernelSyscall(c *CPU) {
|
||||
c.hooks.KernelSyscall()
|
||||
}
|
||||
|
||||
// kernelException is a trampoline.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func kernelException(c *CPU, vector Vector) { c.hooks.KernelException(vector) }
|
||||
func kernelException(c *CPU, vector Vector) {
|
||||
c.hooks.KernelException(vector)
|
||||
}
|
||||
|
||||
// Init initializes a new CPU.
|
||||
//
|
||||
|
||||
@@ -178,6 +178,8 @@ func IsCanonical(addr uint64) bool {
|
||||
//
|
||||
// Precondition: the Rip, Rsp, Fs and Gs registers must be canonical.
|
||||
//
|
||||
// +checkescape:all
|
||||
//
|
||||
//go:nosplit
|
||||
func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
userCR3 := switchOpts.PageTables.CR3(!switchOpts.Flush, switchOpts.UserPCID)
|
||||
@@ -192,9 +194,9 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
|
||||
// Perform the switch.
|
||||
swapgs() // GS will be swapped on return.
|
||||
WriteFS(uintptr(regs.Fs_base)) // Set application FS.
|
||||
WriteGS(uintptr(regs.Gs_base)) // Set application GS.
|
||||
LoadFloatingPoint(switchOpts.FloatingPointState) // Copy in floating point.
|
||||
WriteFS(uintptr(regs.Fs_base)) // escapes: no. Set application FS.
|
||||
WriteGS(uintptr(regs.Gs_base)) // escapes: no. Set application GS.
|
||||
LoadFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy in floating point.
|
||||
jumpToKernel() // Switch to upper half.
|
||||
writeCR3(uintptr(userCR3)) // Change to user address space.
|
||||
if switchOpts.FullRestore {
|
||||
@@ -204,8 +206,8 @@ func (c *CPU) SwitchToUser(switchOpts SwitchOpts) (vector Vector) {
|
||||
}
|
||||
writeCR3(uintptr(kernelCR3)) // Return to kernel address space.
|
||||
jumpToUser() // Return to lower half.
|
||||
SaveFloatingPoint(switchOpts.FloatingPointState) // Copy out floating point.
|
||||
WriteFS(uintptr(c.registers.Fs_base)) // Restore kernel FS.
|
||||
SaveFloatingPoint(switchOpts.FloatingPointState) // escapes: no. Copy out floating point.
|
||||
WriteFS(uintptr(c.registers.Fs_base)) // escapes: no. Restore kernel FS.
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -53,9 +53,14 @@ type RuntimeAllocator struct {
|
||||
|
||||
// NewRuntimeAllocator returns an allocator that uses runtime allocation.
|
||||
func NewRuntimeAllocator() *RuntimeAllocator {
|
||||
return &RuntimeAllocator{
|
||||
used: make(map[*PTEs]struct{}),
|
||||
}
|
||||
r := new(RuntimeAllocator)
|
||||
r.Init()
|
||||
return r
|
||||
}
|
||||
|
||||
// Init initializes a RuntimeAllocator.
|
||||
func (r *RuntimeAllocator) Init() {
|
||||
r.used = make(map[*PTEs]struct{})
|
||||
}
|
||||
|
||||
// Recycle returns freed pages to the pool.
|
||||
|
||||
@@ -86,6 +86,8 @@ func (*mapVisitor) requiresSplit() bool { return true }
|
||||
//
|
||||
// Precondition: addr & length must be page-aligned, their sum must not overflow.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (p *PageTables) Map(addr usermem.Addr, length uintptr, opts MapOpts, physical uintptr) bool {
|
||||
if !opts.AccessType.Any() {
|
||||
@@ -128,6 +130,8 @@ func (v *unmapVisitor) visit(start uintptr, pte *PTE, align uintptr) {
|
||||
//
|
||||
// Precondition: addr & length must be page-aligned.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (p *PageTables) Unmap(addr usermem.Addr, length uintptr) bool {
|
||||
w := unmapWalker{
|
||||
@@ -162,6 +166,8 @@ func (v *emptyVisitor) visit(start uintptr, pte *PTE, align uintptr) {
|
||||
//
|
||||
// Precondition: addr & length must be page-aligned.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (p *PageTables) IsEmpty(addr usermem.Addr, length uintptr) bool {
|
||||
w := emptyWalker{
|
||||
@@ -197,6 +203,8 @@ func (*lookupVisitor) requiresSplit() bool { return false }
|
||||
|
||||
// Lookup returns the physical address for the given virtual address.
|
||||
//
|
||||
// +checkescape:hard,stack
|
||||
//
|
||||
//go:nosplit
|
||||
func (p *PageTables) Lookup(addr usermem.Addr) (physical uintptr, opts MapOpts) {
|
||||
mask := uintptr(usermem.PageSize - 1)
|
||||
|
||||
Reference in New Issue
Block a user