github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/platform/kvm/bluepill_fault.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kvm 16 17 import ( 18 "sync/atomic" 19 20 "golang.org/x/sys/unix" 21 "github.com/SagerNet/gvisor/pkg/hostarch" 22 ) 23 24 const ( 25 // faultBlockSize is the size used for servicing memory faults. 26 // 27 // This should be large enough to avoid frequent faults and avoid using 28 // all available KVM slots (~512), but small enough that KVM does not 29 // complain about slot sizes (~4GB). See handleBluepillFault for how 30 // this block is used. 31 faultBlockSize = 2 << 30 32 33 // faultBlockMask is the mask for the fault blocks. 34 // 35 // This must be typed to avoid overflow complaints (ugh). 36 faultBlockMask = ^uintptr(faultBlockSize - 1) 37 ) 38 39 // yield yields the CPU. 40 // 41 //go:nosplit 42 func yield() { 43 unix.RawSyscall(unix.SYS_SCHED_YIELD, 0, 0, 0) 44 } 45 46 // calculateBluepillFault calculates the fault address range. 47 // 48 //go:nosplit 49 func calculateBluepillFault(physical uintptr, phyRegions []physicalRegion) (virtualStart, physicalStart, length uintptr, ok bool) { 50 alignedPhysical := physical &^ uintptr(hostarch.PageSize-1) 51 for _, pr := range phyRegions { 52 end := pr.physical + pr.length 53 if physical < pr.physical || physical >= end { 54 continue 55 } 56 57 // Adjust the block to match our size. 58 physicalStart = alignedPhysical & faultBlockMask 59 if physicalStart < pr.physical { 60 // Bound the starting point to the start of the region. 61 physicalStart = pr.physical 62 } 63 virtualStart = pr.virtual + (physicalStart - pr.physical) 64 physicalEnd := physicalStart + faultBlockSize 65 if physicalEnd > end { 66 physicalEnd = end 67 } 68 length = physicalEnd - physicalStart 69 return virtualStart, physicalStart, length, true 70 } 71 72 return 0, 0, 0, false 73 } 74 75 // handleBluepillFault handles a physical fault. 76 // 77 // The corresponding virtual address is returned. This may throw on error. 78 // 79 //go:nosplit 80 func handleBluepillFault(m *machine, physical uintptr, phyRegions []physicalRegion, flags uint32) (uintptr, bool) { 81 // Paging fault: we need to map the underlying physical pages for this 82 // fault. This all has to be done in this function because we're in a 83 // signal handler context. (We can't call any functions that might 84 // split the stack.) 85 virtualStart, physicalStart, length, ok := calculateBluepillFault(physical, phyRegions) 86 if !ok { 87 return 0, false 88 } 89 90 // Set the KVM slot. 91 // 92 // First, we need to acquire the exclusive right to set a slot. See 93 // machine.nextSlot for information about the protocol. 94 slot := atomic.SwapUint32(&m.nextSlot, ^uint32(0)) 95 for slot == ^uint32(0) { 96 yield() // Race with another call. 97 slot = atomic.SwapUint32(&m.nextSlot, ^uint32(0)) 98 } 99 errno := m.setMemoryRegion(int(slot), physicalStart, length, virtualStart, flags) 100 if errno == 0 { 101 // Store the physical address in the slot. This is used to 102 // avoid calls to handleBluepillFault in the future (see 103 // machine.mapPhysical). 104 atomic.StoreUintptr(&m.usedSlots[slot], physical) 105 // Successfully added region; we can increment nextSlot and 106 // allow another set to proceed here. 107 atomic.StoreUint32(&m.nextSlot, slot+1) 108 return virtualStart + (physical - physicalStart), true 109 } 110 111 // Release our slot (still available). 112 atomic.StoreUint32(&m.nextSlot, slot) 113 114 switch errno { 115 case unix.EEXIST: 116 // The region already exists. It's possible that we raced with 117 // another vCPU here. We just revert nextSlot and return true, 118 // because this must have been satisfied by some other vCPU. 119 return virtualStart + (physical - physicalStart), true 120 case unix.EINVAL: 121 throw("set memory region failed; out of slots") 122 case unix.ENOMEM: 123 throw("set memory region failed: out of memory") 124 case unix.EFAULT: 125 throw("set memory region failed: invalid physical range") 126 default: 127 throw("set memory region failed: unknown reason") 128 } 129 130 panic("unreachable") 131 }