From 5c544aaccb1e535413c9f76fc185636e7600899c Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Thu, 31 Mar 2022 17:43:50 -0700 Subject: [PATCH] kvm: add a few sanity checks on vmexit-s Here are not visible perfomance effects: Before: goos: linux goarch: amd64 cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz BenchmarkApplicationSyscall I0401 16:23:28.351151 2594711 physical_map.go:145] region: virtual [3f1eaf85f000,7f202f85f000) I0401 16:23:28.351260 2594711 physical_map.go:197] physicalRegion: virtual [1000,3f1eaf85f000) => physical [100001000,3f1faf85f000) I0401 16:23:28.351287 2594711 physical_map.go:197] physicalRegion: virtual [7f202f85f000,7ffffffff000) => physical [3f1faf85f000,3fff7ffff000) BenchmarkApplicationSyscall-6 2574579 408.2 ns/op BenchmarkKernelSyscall BenchmarkKernelSyscall-6 2268025 500.0 ns/op BenchmarkKernelVDSO BenchmarkKernelVDSO-6 26895987 41.30 ns/op BenchmarkWorldSwitchToUserRoundtrip BenchmarkWorldSwitchToUserRoundtrip-6 188020 5655 ns/op After: goos: linux goarch: amd64 cpu: Intel(R) Xeon(R) W-2135 CPU @ 3.70GHz BenchmarkApplicationSyscall I0401 16:23:56.143275 2595618 physical_map.go:145] region: virtual [3f5ec46d0000,7f60446d0000) I0401 16:23:56.143387 2595618 physical_map.go:197] physicalRegion: virtual [1000,3f5ec46d0000) => physical [100001000,3f5fc46d0000) I0401 16:23:56.143403 2595618 physical_map.go:197] physicalRegion: virtual [7f60446d0000,7ffffffff000) => physical [3f5fc46d0000,3fff7ffff000) BenchmarkApplicationSyscall-6 2798324 409.8 ns/op BenchmarkKernelSyscall BenchmarkKernelSyscall-6 2314084 490.8 ns/op BenchmarkKernelVDSO BenchmarkKernelVDSO-6 26801140 40.11 ns/op BenchmarkWorldSwitchToUserRoundtrip BenchmarkWorldSwitchToUserRoundtrip-6 202519 5739 ns/op PASS --- pkg/ring0/defs_amd64.go | 20 +++++++++++++++++ pkg/ring0/defs_arm64.go | 4 +++- pkg/ring0/entry_amd64.s | 7 ++++++ pkg/sentry/platform/kvm/bluepill_amd64.go | 26 ++++++++++++++++++++++ pkg/sentry/platform/kvm/bluepill_arm64.go | 6 +++++ pkg/sentry/platform/kvm/bluepill_unsafe.go | 17 ++++++++++++++ pkg/sentry/platform/kvm/machine_arm64.go | 2 +- 7 files changed, 80 insertions(+), 2 deletions(-) diff --git a/pkg/ring0/defs_amd64.go b/pkg/ring0/defs_amd64.go index bc271f8e2..a9cfeee46 100644 --- a/pkg/ring0/defs_amd64.go +++ b/pkg/ring0/defs_amd64.go @@ -129,6 +129,12 @@ type CPUArchState struct { // exception. errorType uintptr + // vector is the vector of the last exception. + vector uintptr + + // faultAddr is the value of the cr2 register. + faultAddr uintptr + *kernelEntry // Copies of global variables, stored in CPU so that they can be used by @@ -157,6 +163,20 @@ func (c *CPU) ClearErrorCode() { c.errorType = 1 // User mode. } +// Vector returns the vector of the last exception. +// +//go:nosplit +func (c *CPU) Vector() uintptr { + return c.vector +} + +// FaultAddr returns the last fault address. +// +//go:nosplit +func (c *CPU) FaultAddr() uintptr { + return c.faultAddr +} + // SwitchArchOpts are embedded in SwitchOpts. type SwitchArchOpts struct { // UserPCID indicates that the application PCID to be used on switch, diff --git a/pkg/ring0/defs_arm64.go b/pkg/ring0/defs_arm64.go index 7d281cf98..793ea83c1 100644 --- a/pkg/ring0/defs_arm64.go +++ b/pkg/ring0/defs_arm64.go @@ -98,8 +98,10 @@ func (c *CPU) ClearErrorCode() { c.errorType = 1 // User mode. } +// FaultAddr returns the last fault address. +// //go:nosplit -func (c *CPU) GetFaultAddr() (value uintptr) { +func (c *CPU) FaultAddr() (value uintptr) { return c.faultAddr } diff --git a/pkg/ring0/entry_amd64.s b/pkg/ring0/entry_amd64.s index f2c3608d4..775a46900 100644 --- a/pkg/ring0/entry_amd64.s +++ b/pkg/ring0/entry_amd64.s @@ -20,6 +20,8 @@ #define CPU_FPU_STATE {{ .CPU.floatingPointState.Offset }} #define CPU_ERROR_CODE ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.errorCode.Offset }}) #define CPU_ERROR_TYPE ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.errorType.Offset }}) +#define CPU_VECTOR ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.vector.Offset }}) +#define CPU_FAULT_ADDR ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.faultAddr.Offset }}) #define CPU_ENTRY ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.kernelEntry.Offset }}) #define CPU_HAS_XSAVE ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.hasXSAVE.Offset }}) #define CPU_HAS_XSAVEOPT ({{ .CPU.CPUArchState.Offset }}+{{ .CPUArchState.hasXSAVEOPT.Offset }}) @@ -488,6 +490,7 @@ kernel: MOVQ BX, CPU_REGISTERS+PTRACE_RAX(AX) MOVQ $0, CPU_ERROR_CODE(AX) // Clear error code. MOVQ $0, CPU_ERROR_TYPE(AX) // Set error type to kernel. + MOVQ $0xffffffffffffffff, CPU_VECTOR(AX) // Set error type to kernel. // Save floating point state. CPU.floatingPointState is a slice, so the // first word of CPU.floatingPointState is a pointer to the destination @@ -608,6 +611,10 @@ kernel: // Set the error code and adjust the stack. MOVQ 8(SP), BX // Load the error code. MOVQ BX, CPU_ERROR_CODE(AX) // Copy out to the CPU. + MOVQ 0(SP), BX // Load the error code. + MOVQ BX, CPU_VECTOR(AX) // Copy out to the CPU. + BYTE $0x0f; BYTE $0x20; BYTE $0xd3; // MOV CR2, RBX + MOVQ BX, CPU_FAULT_ADDR(AX) MOVQ $0, CPU_ERROR_TYPE(AX) // Set error type to kernel. // Save floating point state. CPU.floatingPointState is a slice, so the diff --git a/pkg/sentry/platform/kvm/bluepill_amd64.go b/pkg/sentry/platform/kvm/bluepill_amd64.go index b2db2bb9f..0ba7a0d0f 100644 --- a/pkg/sentry/platform/kvm/bluepill_amd64.go +++ b/pkg/sentry/platform/kvm/bluepill_amd64.go @@ -61,6 +61,32 @@ func bluepillArchEnter(context *arch.SignalContext64) *vCPU { return c } +// hltSanityCheck verifies the current state to detect obvious corruption. +// +//go:nosplit +func (c *vCPU) hltSanityCheck() { + vector := c.CPU.Vector() + switch ring0.Vector(vector) { + case ring0.PageFault: + if c.CPU.FaultAddr() < ring0.KernelStartAddress { + return + } + case ring0.DoubleFault: + case ring0.GeneralProtectionFault: + case ring0.InvalidOpcode: + case ring0.MachineCheck: + case ring0.VirtualizationException: + default: + return + } + + printHex([]byte("Vector = "), uint64(c.CPU.Vector())) + printHex([]byte("FaultAddr = "), uint64(c.CPU.FaultAddr())) + printHex([]byte("rip = "), uint64(c.CPU.Registers().Rip)) + printHex([]byte("rsp = "), uint64(c.CPU.Registers().Rsp)) + throw("fault") +} + // KernelSyscall handles kernel syscalls. // // +checkescape:all diff --git a/pkg/sentry/platform/kvm/bluepill_arm64.go b/pkg/sentry/platform/kvm/bluepill_arm64.go index df772d620..f28e2a4e6 100644 --- a/pkg/sentry/platform/kvm/bluepill_arm64.go +++ b/pkg/sentry/platform/kvm/bluepill_arm64.go @@ -124,3 +124,9 @@ func (c *vCPU) KernelException(vector ring0.Vector) { ring0.Halt() } + +// hltSanityCheck verifies the current state to detect obvious corruption. +// +//go:nosplit +func (c *vCPU) hltSanityCheck() { +} diff --git a/pkg/sentry/platform/kvm/bluepill_unsafe.go b/pkg/sentry/platform/kvm/bluepill_unsafe.go index 4920b78d5..cc6949d6c 100644 --- a/pkg/sentry/platform/kvm/bluepill_unsafe.go +++ b/pkg/sentry/platform/kvm/bluepill_unsafe.go @@ -80,6 +80,21 @@ func bluepillGuestExit(c *vCPU, context unsafe.Pointer) { } } +var hexSyms = []byte("0123456789abcdef") + +//go:nosplit +func printHex(title []byte, val uint64) { + var str [18]byte + for i := 0; i < 16; i++ { + str[16-i] = hexSyms[val&0xf] + val = val >> 4 + } + str[0] = ' ' + str[17] = '\n' + unix.RawSyscall(unix.SYS_WRITE, uintptr(unix.Stderr), uintptr(unsafe.Pointer(&title[0])), uintptr(len(title))) + unix.RawSyscall(unix.SYS_WRITE, uintptr(unix.Stderr), uintptr(unsafe.Pointer(&str)), 18) +} + // bluepillHandler is called from the signal stub. // // The world may be stopped while this is executing, and it executes on the @@ -185,6 +200,7 @@ func bluepillHandler(context unsafe.Pointer) { c.die(bluepillArchContext(context), "debug") return case _KVM_EXIT_HLT: + c.hltSanityCheck() bluepillGuestExit(c, context) return case _KVM_EXIT_MMIO: @@ -205,6 +221,7 @@ func bluepillHandler(context unsafe.Pointer) { c.die(bluepillArchContext(context), "entry failed") return default: + printHex([]byte("exitReason="), uint64(c.runData.exitReason)) bluepillArchHandleExit(c, context) return } diff --git a/pkg/sentry/platform/kvm/machine_arm64.go b/pkg/sentry/platform/kvm/machine_arm64.go index f79657976..cbdc42e84 100644 --- a/pkg/sentry/platform/kvm/machine_arm64.go +++ b/pkg/sentry/platform/kvm/machine_arm64.go @@ -168,7 +168,7 @@ func isWriteFault(code uint64) bool { //go:nosplit func (c *vCPU) fault(signal int32, info *linux.SignalInfo) (hostarch.AccessType, error) { bluepill(c) // Probably no-op, but may not be. - faultAddr := c.GetFaultAddr() + faultAddr := c.FaultAddr() code, user := c.ErrorCode() if !user { // The last fault serviced by this CPU was not a user