Refactor context-related shared memory usage.

This change introduces an abstraction for most accesses to shared thread-context
memory. In general, there are very few cases where accessing this memory is not
supposed to be atomic, so it makes sense to abstract these accesses into
getters/setters that perform the actions atomically. After this change, we
should treat most direct accesses through sharedContext.shared as suspect.

Additionally this cleanup allows the new sharedContext instance to become the
context interruptor. When doing this it is no longer required to use locks, as
was done in context.NotifyInterrupt.

PiperOrigin-RevId: 517166307
This commit is contained in:
Konstantin Bogomolov
2023-03-16 10:50:15 -07:00
committed by gVisor bot
parent fedadb0932
commit adde0cc814
10 changed files with 287 additions and 302 deletions
+1 -2
View File
@@ -28,15 +28,14 @@ go_library(
"filters_arm64.go",
"lib_amd64.s",
"lib_arm64.s",
"shared_context.go",
"stub_amd64.s",
"stub_arm64.s",
"stub_defs.go",
"stub_unsafe.go",
"subprocess.go",
"subprocess_amd64.go",
"subprocess_amd64_unsafe.go",
"subprocess_arm64.go",
"subprocess_arm64_unsafe.go",
"subprocess_linux.go",
"subprocess_linux_unsafe.go",
"subprocess_pool.go",
@@ -0,0 +1,168 @@
// Copyright 2023 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 systrap
import (
"fmt"
"sync/atomic"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
const (
ackReset uint32 = 0
)
// sharedContext is an abstraction for interactions that the sentry has to
// perform with memory shared between it and the stub threads used for contexts.
//
// Any access to shared memory should most likely have a getter/setter through
// this struct. This is due to the following reasons:
// - The memory needs to be read or modified atomically because there is no
// (trusted) synchronization between the sentry and the stub processes.
// - Data read from shared memory may require validation before it can be used.
type sharedContext struct {
// subprocess is the subprocess that this sharedContext instance belongs to.
subprocess *subprocess
// contextID is the ID corresponding to the sysmsg.ThreadContext memory slot
// that is used for this sharedContext.
contextID uint64
// shared is the handle to the shared memory that the sentry task goroutine
// reads from and writes to.
// NOTE: Using this handle directly without a getter from this function should
// most likely be avoided due to concerns listed above.
shared *sysmsg.ThreadContext
}
func (s *subprocess) getSharedContext() (*sharedContext, error) {
s.mu.Lock()
defer s.mu.Unlock()
id, ok := s.threadContextPool.Get()
if !ok {
return nil, fmt.Errorf("subprocess has too many active tasks (%d); failed to create a new one", maxGuestContexts)
}
s.IncRef()
sc := sharedContext{
subprocess: s,
contextID: id,
shared: s.getThreadContextFromID(id),
}
sc.shared.Init(invalidThreadID)
return &sc, nil
}
func (sc *sharedContext) release() {
if sc == nil {
return
}
sc.subprocess.threadContextPool.Put(sc.contextID)
sc.subprocess.DecRef(sc.subprocess.release)
}
func (sc *sharedContext) isActiveInSubprocess(s *subprocess) bool {
if sc == nil {
return false
}
return sc.subprocess == s
}
// NotifyInterrupt implements interrupt.Receiver.NotifyInterrupt.
func (sc *sharedContext) NotifyInterrupt() {
// If this context is not being worked on right now we need to mark it as
// interrupted so the next executor does not start working on it.
atomic.StoreUint32(&sc.shared.Interrupt, 1)
if sc.threadID() == invalidThreadID {
return
}
sc.subprocess.sysmsgThreadsMu.Lock()
defer sc.subprocess.sysmsgThreadsMu.Unlock()
threadID := atomic.LoadUint32(&sc.shared.ThreadID)
sysmsgThread, ok := sc.subprocess.sysmsgThreads[threadID]
if !ok {
// This is either an invalidThreadID or another garbage value; either way we
// don't know which thread to interrupt; best we can do is mark the context.
return
}
t := sysmsgThread.thread
atomic.StoreUint64(&sysmsgThread.msg.InterruptedContextID, sc.contextID)
if _, _, e := unix.RawSyscall(unix.SYS_TGKILL, uintptr(t.tgid), uintptr(t.tid), uintptr(platform.SignalInterrupt)); e != 0 {
panic(fmt.Sprintf("failed to interrupt the child process %d: %v", t.tid, e))
}
}
func (sc *sharedContext) state() sysmsg.ContextState {
return sc.shared.State.Get()
}
func (sc *sharedContext) setState(state sysmsg.ContextState) {
sc.shared.State.Set(state)
}
func (sc *sharedContext) setInterrupt() {
atomic.StoreUint32(&sc.shared.Interrupt, 1)
}
func (sc *sharedContext) clearInterrupt() {
atomic.StoreUint32(&sc.shared.Interrupt, 0)
}
func (sc *sharedContext) setFPStateChanged() {
atomic.StoreUint64(&sc.shared.FPStateChanged, 1)
}
func (sc *sharedContext) threadID() uint32 {
return atomic.LoadUint32(&sc.shared.ThreadID)
}
func (sc *sharedContext) setThreadID(threadID uint32) {
if contextDecouplingExp {
panic("context decoupled systrap should never explicitly set ThreadID")
}
atomic.StoreUint32(&sc.shared.ThreadID, threadID)
}
// EnableSentryFastPath indicates that the polling mode is enabled for the
// Sentry. It has to be called before putting the context into the context queue.
// This function is used if contextDecouplingExp=true because the fastpath
// is negotiated in ThreadContext.
func (sc *sharedContext) enableSentryFastPath() {
atomic.StoreUint32(&sc.shared.SentryFastPath, 1)
}
// DisableSentryFastPath indicates that the polling mode for the sentry is
// disabled for the Sentry.
// This function is used if contextDecouplingExp=true because the fastpath
// is negotiated in ThreadContext.
func (sc *sharedContext) disableSentryFastPath() {
atomic.StoreUint32(&sc.shared.SentryFastPath, 0)
}
func (sc *sharedContext) isAcked() bool {
return atomic.LoadUint32(&sc.shared.Acked) != ackReset
}
func (sc *sharedContext) resetAcked() {
atomic.StoreUint32(&sc.shared.Acked, ackReset)
}
func (sc *sharedContext) sleepOnState(state sysmsg.ContextState) {
sc.shared.SleepOnState(state)
}
+47 -90
View File
@@ -680,9 +680,9 @@ func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool
// Reset necessary registers.
regs := &ac.StateData().Regs
s.resetSysemuRegs(regs)
ctx := s.getThreadContextFromID(c.cid)
ctx.Regs = regs.PtraceRegs
restoreArchSpecificState(ctx, ac)
ctx := c.sharedContext
ctx.shared.Regs = regs.PtraceRegs
restoreArchSpecificState(ctx.shared, ac)
// Get sysmsg thread bound to the context; no-op if contextDecoupling is on.
sysThread, err := s.getSysmsgThread(regs, c, ac)
@@ -691,36 +691,36 @@ func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool
}
// Check for interrupts, and ensure that future interrupts signal the context.
if !c.interrupt.Enable(c) {
if !c.interrupt.Enable(c.sharedContext) {
// Pending interrupt; simulate.
ctx.Interrupt = 0
ctx.clearInterrupt()
c.signalInfo = linux.SignalInfo{Signo: int32(platform.SignalInterrupt)}
return false, false, nil
}
defer c.interrupt.Disable()
if contextDecouplingExp {
s.restoreFPState(nil, ctx, 0, c, ac)
restoreFPState(nil, ctx, 0, c, ac)
// Place the context onto the context queue.
ctx.State.Set(sysmsg.ContextStateNone)
s.contextQueue.add(uint32(c.cid))
ctx.setState(sysmsg.ContextStateNone)
s.contextQueue.add(uint32(ctx.contextID))
s.waitOnState(ctx)
// Check if there's been an error.
tid := atomic.LoadUint32(&ctx.ThreadID)
if tid != invalidThreadID {
if sysThread, ok := s.sysmsgThreads[tid]; ok && sysThread.msg.Err != 0 {
threadID := ctx.threadID()
if threadID != invalidThreadID {
if sysThread, ok := s.sysmsgThreads[threadID]; ok && sysThread.msg.Err != 0 {
msg := sysThread.msg
panic(fmt.Sprintf("stub thread %d failed: err 0x%x line %d: %s", sysThread.thread.tid, msg.Err, msg.Line, msg))
}
log.Warningf("systrap: found unexpected ThreadContext.ThreadID field, expected %d found %d", invalidThreadID, tid)
log.Warningf("systrap: found unexpected ThreadContext.ThreadID field, expected %d found %d", invalidThreadID, threadID)
}
} else {
msg := sysThread.msg
t := sysThread.thread
s.restoreFPState(msg, ctx, sysThread.fpuStateToMsgOffset, c, ac)
restoreFPState(msg, ctx, sysThread.fpuStateToMsgOffset, c, ac)
msg.EnableSentryFastPath()
sysThread.waitEvent(sysmsg.ThreadStateDone)
@@ -730,7 +730,7 @@ func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool
panic(fmt.Sprintf("stub thread %d failed: err %d line %d: %s", t.tid, msg.Err, msg.Line, msg))
}
if ctx.State != sysmsg.ContextStateSyscallTrap {
if ctx.state() != sysmsg.ContextStateSyscallTrap {
var err error
sysThread.fpuStateToMsgOffset, err = msg.FPUStateOffset()
if err != nil {
@@ -738,27 +738,28 @@ func (s *subprocess) switchToApp(c *context, ac *arch.Context64) (isSyscall bool
}
}
retrieveArchSpecificState(ctx, ac)
retrieveArchSpecificState(ctx.shared, ac)
}
regs.PtraceRegs = ctx.Regs
regs.PtraceRegs = ctx.shared.Regs
// We have a signal. We verify however, that the signal was
// either delivered from the kernel or from this process. We
// don't respect other signals.
c.signalInfo = ctx.SignalInfo
if ctx.State == sysmsg.ContextStateSyscallCanBePatched {
ctx.State = sysmsg.ContextStateSyscall
c.signalInfo = ctx.shared.SignalInfo
ctxState := ctx.state()
if ctxState == sysmsg.ContextStateSyscallCanBePatched {
ctxState = sysmsg.ContextStateSyscall
shouldPatchSyscall = true
}
if ctx.State == sysmsg.ContextStateSyscall || ctx.State == sysmsg.ContextStateSyscallTrap {
if ctxState == sysmsg.ContextStateSyscall || ctxState == sysmsg.ContextStateSyscallTrap {
if maybePatchSignalInfo(regs, &c.signalInfo) {
return false, false, nil
}
updateSyscallRegs(regs)
return true, shouldPatchSyscall, nil
} else if ctx.State != sysmsg.ContextStateFault {
panic(fmt.Sprintf("unknown context state: %v", ctx.State))
} else if ctxState != sysmsg.ContextStateFault {
panic(fmt.Sprintf("unknown context state: %v", ctxState))
}
return false, false, nil
@@ -773,23 +774,21 @@ const (
threadKickTimeout = uint64(20000)
)
func (s *subprocess) waitOnState(ctx *sysmsg.ThreadContext) {
// ackedEvents is always reset to 0 at the end of this function.
ackedEvents := uint32(0)
func (s *subprocess) waitOnState(ctx *sharedContext) {
kicked := false
slowPath := false
start := cputicks()
handshake := false
for curState := ctx.State.Get(); curState == sysmsg.ContextStateNone; curState = ctx.State.Get() {
for curState := ctx.state(); curState == sysmsg.ContextStateNone; curState = ctx.state() {
if !slowPath {
delta := uint64(cputicks() - start)
if delta > decoupledDeepSleepTimeout {
ctx.DisableSentryFastPath()
ctx.disableSentryFastPath()
slowPath = true
continue
}
if !handshake && ackedEvents != atomic.LoadUint32(&ctx.Acked) {
if !handshake && ctx.isAcked() {
handshake = true
continue
}
@@ -802,12 +801,12 @@ func (s *subprocess) waitOnState(ctx *sysmsg.ThreadContext) {
s.kickSysmsgThread()
}
ctx.SleepOnState(curState)
ctx.sleepOnState(curState)
}
}
atomic.StoreUint32(&ctx.Acked, 0)
ctx.EnableSentryFastPath()
ctx.resetAcked()
ctx.enableSentryFastPath()
}
func (s *subprocess) kickSysmsgThread() {
@@ -894,18 +893,17 @@ func (s *subprocess) Unmap(addr hostarch.Addr, length uint64) {
}
func (s *subprocess) PullFullState(c *context, ac *arch.Context64) error {
if s != c.subprocess {
if !c.sharedContext.isActiveInSubprocess(s) {
panic("Attempted to PullFullState for context that is not used in subprocess")
}
ctx := s.getThreadContextFromID(c.cid)
if contextDecouplingExp {
s.saveFPState(nil, ctx, 0, c, ac)
saveFPState(nil, c.sharedContext, 0, c, ac)
} else {
sysThread, err := s.getSysmsgThread(&ac.StateData().Regs, c, ac)
if err != nil {
return err
}
s.saveFPState(sysThread.msg, ctx, sysThread.fpuStateToMsgOffset, c, ac)
saveFPState(sysThread.msg, c.sharedContext, sysThread.fpuStateToMsgOffset, c, ac)
}
return nil
}
@@ -1023,8 +1021,8 @@ func (s *subprocess) createSysmsgThread(tregs *arch.Registers, c *context, ac *a
if contextDecouplingExp {
sysThread.msg.ContextID = uint64(invalidContextID)
} else {
s.getThreadContextFromID(c.cid).ThreadID = threadID
sysThread.msg.ContextID = c.cid
c.sharedContext.setThreadID(threadID)
sysThread.msg.ContextID = c.sharedContext.contextID
}
sysThread.msg.Self = uint64(sysmsgStackAddr + sysmsg.MsgOffsetFromSharedStack)
sysThread.msg.SyshandlerStack = uint64(sysmsg.StackAddrToSyshandlerStack(sysThread.sysmsgPerThreadMemAddr()))
@@ -1100,60 +1098,19 @@ func (s *subprocess) PostFork() {
s.usertrap.PostFork() // +checklocksforce: PreFork acquires, above.
}
// registerContext registers the context to an ID specific to this subprocess.
// It will return an error if too many contexts are already active in this
// subprocess.
func (s *subprocess) registerContext(c *context) error {
s.mu.Lock()
c.mu.Lock()
// Unlock manually for the sake of not holding the lock while initializing
// context memory.
locked := true
unlock := func() {
if locked {
c.mu.Unlock()
s.mu.Unlock()
locked = false
// activateContext activates the context in this subprocess.
// No-op if the context is already active within the subprocess; if not,
// deactivates it from its last subprocess.
func (s *subprocess) activateContext(c *context) error {
if !c.sharedContext.isActiveInSubprocess(s) {
c.sharedContext.release()
c.sharedContext = nil
shared, err := s.getSharedContext()
if err != nil {
return err
}
c.sharedContext = shared
}
defer unlock()
if s == c.subprocess && c.cid != invalidContextID {
return nil
}
id, ok := s.threadContextPool.Get()
if !ok {
return fmt.Errorf("subprocess has too many active threads (%d); failed to create a new one", maxGuestContexts)
}
s.IncRef()
c.cid = id
c.subprocess = s
c.FullStateChanged()
unlock()
threadContext := s.getThreadContextFromID(id)
threadContext.Init(invalidThreadID)
return nil
}
// unregisterContext releases all references held for this context.
//
// Precondition: context c must have been active within subprocess s.
func (s *subprocess) unregisterContext(c *context) {
if s == nil {
return
}
c.mu.Lock()
cid := c.cid
c.cid = invalidContextID
c.subprocess = nil
c.mu.Unlock()
s.mu.Lock()
delete(s.faultedContexts, c)
s.threadContextPool.Put(cid)
s.mu.Unlock()
s.DecRef(s.release)
}
@@ -1,65 +0,0 @@
// Copyright 2018 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.
//go:build amd64
// +build amd64
package systrap
import (
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
//go:nosplit
func isFPStateInContextRegion(ctx *sysmsg.ThreadContext) bool {
// If context decoupling experiment is ON then both the sighandler and
// syshandler save FPState to the context region since contexts will move
// threads. Otherwise only syshandler will save FPState to the region.
return contextDecouplingExp || ctx.State == sysmsg.ContextStateSyscallTrap
}
func (s *subprocess) saveFPState(msg *sysmsg.Msg, ctx *sysmsg.ThreadContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
var src []byte
if isFPStateInContextRegion(ctx) {
src = ctx.FPState[:]
} else {
src = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
}
copy(dst, src)
}
// restoreFPStateDecoupledContext writes FPState from c to the thread context
// shared memory region if there is any need to do so.
func (s *subprocess) restoreFPState(msg *sysmsg.Msg, ctx *sysmsg.ThreadContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
if !c.needRestoreFPState {
return
}
c.needRestoreFPState = false
ctx.FPStateChanged = 1
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
var dst []byte
if isFPStateInContextRegion(ctx) {
dst = ctx.FPState[:]
} else {
dst = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
}
copy(dst, src)
}
@@ -1,65 +0,0 @@
// 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.
//go:build arm64
// +build arm64
package systrap
import (
"unsafe"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
)
//go:nosplit
func isFPStateInContextRegion(ctx *sysmsg.ThreadContext) bool {
// If context decoupling experiment is ON then both the sighandler and
// syshandler save FPState to the context region since contexts will move
// threads. Otherwise only syshandler will save FPState to the region.
return contextDecouplingExp || ctx.State == sysmsg.ContextStateSyscallTrap
}
func (s *subprocess) restoreFPState(msg *sysmsg.Msg, ctx *sysmsg.ThreadContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
// c.needRestoreFPState is changed only from the task goroutine, so it can
// be accessed without locks.
if !c.needRestoreFPState {
return
}
c.needRestoreFPState = false
ctx.FPStateChanged = 1
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
var dst []byte
if isFPStateInContextRegion(ctx) {
dst = ctx.FPState[:]
} else {
dst = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
}
copy(dst, src)
}
func (s *subprocess) saveFPState(msg *sysmsg.Msg, ctx *sysmsg.ThreadContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), c.fpLen)
var src []byte
if isFPStateInContextRegion(ctx) {
src = ctx.FPState[:]
} else {
src = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), c.fpLen)
}
copy(dst, src)
}
@@ -26,6 +26,7 @@ import (
"unsafe"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/memmap"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
@@ -68,3 +69,43 @@ func mmapContextQueueForSentry(memoryFile *pgalloc.MemoryFile, opts pgalloc.Allo
return fr, (*contextQueue)(unsafe.Pointer(addr))
}
//go:nosplit
func isFPStateInContextRegion(ctx *sharedContext) bool {
// If context decoupling experiment is ON then both the sighandler and
// syshandler save FPState to the context region since contexts will move
// threads. Otherwise only syshandler will save FPState to the region.
return contextDecouplingExp || ctx.state() == sysmsg.ContextStateSyscallTrap
}
func saveFPState(msg *sysmsg.Msg, ctx *sharedContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
fpState := ac.FloatingPointData().BytePointer()
dst := unsafeSlice(uintptr(unsafe.Pointer(fpState)), archState.FpLen())
var src []byte
if isFPStateInContextRegion(ctx) {
src = ctx.shared.FPState[:]
} else {
src = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), archState.FpLen())
}
copy(dst, src)
}
// restoreFPStateDecoupledContext writes FPState from c to the thread context
// shared memory region if there is any need to do so.
func restoreFPState(msg *sysmsg.Msg, ctx *sharedContext, fpuToMsgOffset uint64, c *context, ac *arch.Context64) {
if !c.needRestoreFPState {
return
}
c.needRestoreFPState = false
ctx.setFPStateChanged()
fpState := ac.FloatingPointData().BytePointer()
src := unsafeSlice(uintptr(unsafe.Pointer(fpState)), archState.FpLen())
var dst []byte
if isFPStateInContextRegion(ctx) {
dst = ctx.shared.FPState[:]
} else {
dst = unsafeSlice(uintptr(unsafe.Pointer(msg))+uintptr(fpuToMsgOffset), archState.FpLen())
}
copy(dst, src)
}
@@ -337,22 +337,6 @@ func (m *Msg) DisableSentryFastPath() {
atomic.StoreUint32(&m.sentryFastPath, 0)
}
// EnableSentryFastPath indicates that the polling mode is enabled for the
// Sentry. It has to be called before putting the context into the context queue.
// This function is used if contextDecouplingExp=true because the fastpath
// is negotiated in ThreadContext
func (c *ThreadContext) EnableSentryFastPath() {
c.SentryFastPath = 1
}
// DisableSentryFastPath indicates that the polling mode for the sentry is
// disabled for the Sentry.
// This function is used if contextDecouplingExp=true because the fastpath
// is negotiated in ThreadContext.
func (c *ThreadContext) DisableSentryFastPath() {
atomic.StoreUint32(&c.SentryFastPath, 0)
}
// FPUStateOffset returns the offset of a saved FPU state to the msg.
func (m *Msg) FPUStateOffset() (uint64, error) {
offset := m.fpState
@@ -63,6 +63,11 @@ func (s *ArchState) Init() {
}
}
// FpLen returns the FP state length for AMD64.
func (s *ArchState) FpLen() int {
return int(s.fpLen)
}
func (s *ArchState) String() string {
var b strings.Builder
fmt.Fprintf(&b, "sysmsg.ArchState{")
@@ -43,6 +43,11 @@ func (s *ArchState) Init() {
s.fpLen = uint32(fpLenUint)
}
// FpLen returns the FP state length for ARM.
func (s *ArchState) FpLen() int {
return int(s.fpLen)
}
func (s *ArchState) String() string {
var b strings.Builder
fmt.Fprintf(&b, "sysmsg.ArchState{")
+20 -64
View File
@@ -52,18 +52,16 @@ import (
"fmt"
"os"
"sync"
"sync/atomic"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/abi/linux"
pkgcontext "gvisor.dev/gvisor/pkg/context"
"gvisor.dev/gvisor/pkg/cpuid"
"gvisor.dev/gvisor/pkg/hostarch"
"gvisor.dev/gvisor/pkg/memutil"
"gvisor.dev/gvisor/pkg/sentry/arch"
"gvisor.dev/gvisor/pkg/sentry/pgalloc"
"gvisor.dev/gvisor/pkg/sentry/platform"
"gvisor.dev/gvisor/pkg/sentry/platform/interrupt"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/sysmsg"
"gvisor.dev/gvisor/pkg/sentry/platform/systrap/usertrap"
)
@@ -100,6 +98,9 @@ var (
// stubInitialized controls one-time stub initialization.
stubInitialized sync.Once
// archState stores architecture-specific details used in the platform.
archState sysmsg.ArchState
)
// context is an implementation of the platform context.
@@ -110,16 +111,15 @@ type context struct {
// interrupt is the interrupt context.
interrupt interrupt.Forwarder
// sharedContext is everything related to this context that is resident in
// shared memory with the stub thread.
// sharedContext is only accessed on the Task goroutine, therefore it is not
// mutex protected.
sharedContext *sharedContext
// mu protects the following fields.
mu sync.Mutex
// subprocess is the current subprocess used to execute the context.
subprocess *subprocess
// cid is the ID of the context in the address space of the current
// subprocess used to run it.
cid uint64
// If lastFaultSP is non-nil, the last context switch was due to a fault
// received while executing lastFaultSP. Only context.Switch may set
// lastFaultSP to a non-nil value.
@@ -137,9 +137,6 @@ type context struct {
// application code. (Note: Unused if contextDecouplingExp=true).
sysmsgThread *sysmsgThread
// fpLen is the size of the floating point context.
fpLen int
// needRestoreFPState indicates that the FPU state has been changed by
// the Sentry and has to be updated on the stub thread.
needRestoreFPState bool
@@ -174,13 +171,10 @@ func (c *context) Switch(ctx pkgcontext.Context, mm platform.MemoryManager, ac *
as := mm.AddressSpace()
s := as.(*subprocess)
if s != c.subprocess {
c.subprocess.unregisterContext(c)
if err := s.registerContext(c); err != nil {
return nil, hostarch.NoAccess, err
}
if err := s.activateContext(c); err != nil {
return nil, hostarch.NoAccess, err
}
restart:
isSyscall, needPatch, err := s.switchToApp(c, ac)
if err != nil {
@@ -281,52 +275,15 @@ func (c *context) Interrupt() {
c.interrupt.NotifyInterrupt()
}
// NotifyInterrupt implements interrupt.Receiver.NotifyInterrupt.
//
// Another reasonable existing object to implement NotifyInterrupt would be
// sysmsg.ThreadContext, because we can write the correct host TID into it
// to know which thread to send the signal to. However, because it is in shared
// memory, one subprocess can overwrite it to have the sentry send an interrupt
// to a completely different subprocess.
// For this reason we use systrap.context and check that the target thread
// is actually valid within the subprocess.
func (c *context) NotifyInterrupt() {
c.mu.Lock()
s := c.subprocess
cid := c.cid
c.mu.Unlock()
if s == nil || cid == invalidContextID {
return
}
threadContext := s.getThreadContextFromID(cid)
atomic.StoreUint32(&threadContext.Interrupt, 1)
threadID := atomic.LoadUint32(&threadContext.ThreadID)
s.sysmsgThreadsMu.Lock()
defer s.sysmsgThreadsMu.Unlock()
sysmsgThread, ok := s.sysmsgThreads[threadID]
if !ok {
// This is either an invalidThreadID or another garbage value; either way we
// don't know which thread to interrupt; best we can do is mark the context.
return
}
t := sysmsgThread.thread
atomic.StoreUint64(&sysmsgThread.msg.InterruptedContextID, cid)
if _, _, e := unix.RawSyscall(unix.SYS_TGKILL, uintptr(t.tgid), uintptr(t.tid), uintptr(platform.SignalInterrupt)); e != 0 {
panic(fmt.Sprintf("failed to interrupt the child process %d: %v", t.tid, e))
}
}
// Release releases all platform resources used by the context.
func (c *context) Release() {
if c.sysmsgThread != nil {
c.sysmsgThread.destroy()
}
c.subprocess.unregisterContext(c)
if c.sharedContext != nil {
c.sharedContext.release()
c.sharedContext = nil
}
}
// PrepareSleep implements platform.Context.platform.PrepareSleep.
@@ -356,6 +313,9 @@ func (*Systrap) MinUserAddress() hostarch.Addr {
// New returns a new seccomp-based implementation of the platform interface.
func New() (*Systrap, error) {
// CPUID information has been initialized at this point.
archState.Init()
mf, err := createMemoryFile()
if err != nil {
return nil, err
@@ -412,11 +372,7 @@ func (p *Systrap) NewAddressSpace(any) (platform.AddressSpace, <-chan struct{},
// NewContext returns an interruptible context.
func (*Systrap) NewContext(ctx pkgcontext.Context) platform.Context {
fs := cpuid.FromContext(ctx)
fpLen, _ := fs.ExtendedStateSize()
return &context{
cid: invalidContextID,
fpLen: int(fpLen),
needRestoreFPState: true,
needToPullFullState: false,
}