From d26984515900a2f88da047ee8a28ba1ca152aa58 Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Wed, 6 Jun 2018 23:25:26 -0700 Subject: Ensure guest-mode for page table modifications. Because of the KVM shadow page table implementation, modifications made to guest page tables from host mode may not be syncronized correctly, resulting in undefined behavior. This is a KVM bug: page table pages should also be tracked for host modifications and resynced appropriately (e.g. the guest could "DMA" into a page table page in theory). However, since we can't rely on this being fixed everywhere, workaround the issue by forcing page table modifications to be in guest mode. This will generally be the case anyways, but now if an exit occurs during modifications, we will re-enter and perform the modifications again. PiperOrigin-RevId: 199587895 Change-Id: I83c20b4cf2a9f9fa56f59f34939601dd34538fb0 --- pkg/sentry/platform/kvm/address_space.go | 24 ++++++++++++++++++------ pkg/sentry/platform/kvm/machine_amd64.go | 23 +++++++++++++++++++++++ pkg/sentry/platform/ring0/defs_amd64.go | 8 ++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) (limited to 'pkg/sentry/platform') diff --git a/pkg/sentry/platform/kvm/address_space.go b/pkg/sentry/platform/kvm/address_space.go index c2f4559a0..f74c98dd0 100644 --- a/pkg/sentry/platform/kvm/address_space.go +++ b/pkg/sentry/platform/kvm/address_space.go @@ -102,11 +102,18 @@ 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. - prev := as.pageTables.Map(addr, length, pagetables.MapOpts{ - AccessType: at, - User: true, - }, physical) - inv = inv || prev + // + // 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 + }) m.addr += length m.length -= length addr += usermem.Addr(length) @@ -214,7 +221,12 @@ func (as *addressSpace) Unmap(addr usermem.Addr, length uint64) { as.mu.Lock() defer as.mu.Unlock() - if prev := as.pageTables.Unmap(addr, uintptr(length)); prev { + // See above re: retryInGuest. + var prev bool + as.machine.retryInGuest(func() { + prev = as.pageTables.Unmap(addr, uintptr(length)) || prev + }) + if prev { as.invalidate() as.files.DeleteMapping(usermem.AddrRange{ Start: addr, diff --git a/pkg/sentry/platform/kvm/machine_amd64.go b/pkg/sentry/platform/kvm/machine_amd64.go index 6afae5cae..7fcb7451f 100644 --- a/pkg/sentry/platform/kvm/machine_amd64.go +++ b/pkg/sentry/platform/kvm/machine_amd64.go @@ -227,3 +227,26 @@ func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts) (*arch.SignalInfo, user panic(fmt.Sprintf("unexpected vector: 0x%x", vector)) } } + +// 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 + } + } +} diff --git a/pkg/sentry/platform/ring0/defs_amd64.go b/pkg/sentry/platform/ring0/defs_amd64.go index 0d068c00a..84819f132 100644 --- a/pkg/sentry/platform/ring0/defs_amd64.go +++ b/pkg/sentry/platform/ring0/defs_amd64.go @@ -104,6 +104,14 @@ func (c *CPU) ErrorCode() (value uintptr, user bool) { return c.errorCode, c.errorType != 0 } +// ClearErrorCode resets the error code. +// +//go:nosplit +func (c *CPU) ClearErrorCode() { + c.errorCode = 0 // No code. + c.errorType = 1 // User mode. +} + // SwitchArchOpts are embedded in SwitchOpts. type SwitchArchOpts struct { // UserPCID indicates that the application PCID to be used on switch, -- cgit v1.2.3