mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Implement rseq(2)
PiperOrigin-RevId: 288342928
This commit is contained in:
committed by
gVisor bot
parent
6410387ff9
commit
354a15a234
@@ -41,6 +41,7 @@ go_library(
|
||||
"poll.go",
|
||||
"prctl.go",
|
||||
"ptrace.go",
|
||||
"rseq.go",
|
||||
"rusage.go",
|
||||
"sched.go",
|
||||
"seccomp.go",
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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.
|
||||
|
||||
package linux
|
||||
|
||||
// Flags passed to rseq(2).
|
||||
//
|
||||
// Defined in include/uapi/linux/rseq.h.
|
||||
const (
|
||||
// RSEQ_FLAG_UNREGISTER unregisters the current thread.
|
||||
RSEQ_FLAG_UNREGISTER = 1 << 0
|
||||
)
|
||||
|
||||
// Critical section flags used in RSeqCriticalSection.Flags and RSeq.Flags.
|
||||
//
|
||||
// Defined in include/uapi/linux/rseq.h.
|
||||
const (
|
||||
// RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT inhibits restart on preemption.
|
||||
RSEQ_CS_FLAG_NO_RESTART_ON_PREEMPT = 1 << 0
|
||||
|
||||
// RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL inhibits restart on signal
|
||||
// delivery.
|
||||
RSEQ_CS_FLAG_NO_RESTART_ON_SIGNAL = 1 << 1
|
||||
|
||||
// RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE inhibits restart on CPU
|
||||
// migration.
|
||||
RSEQ_CS_FLAG_NO_RESTART_ON_MIGRATE = 1 << 2
|
||||
)
|
||||
|
||||
// RSeqCriticalSection describes a restartable sequences critical section. It
|
||||
// is equivalent to struct rseq_cs, defined in include/uapi/linux/rseq.h.
|
||||
//
|
||||
// In userspace, this structure is always aligned to 32 bytes.
|
||||
//
|
||||
// +marshal
|
||||
type RSeqCriticalSection struct {
|
||||
// Version is the version of this structure. Version 0 is defined here.
|
||||
Version uint32
|
||||
|
||||
// Flags are the critical section flags, defined above.
|
||||
Flags uint32
|
||||
|
||||
// Start is the start address of the critical section.
|
||||
Start uint64
|
||||
|
||||
// PostCommitOffset is the offset from Start of the first instruction
|
||||
// outside of the critical section.
|
||||
PostCommitOffset uint64
|
||||
|
||||
// Abort is the abort address. It must be outside the critical section,
|
||||
// and the 4 bytes prior must match the abort signature.
|
||||
Abort uint64
|
||||
}
|
||||
|
||||
const (
|
||||
// SizeOfRSeqCriticalSection is the size of RSeqCriticalSection.
|
||||
SizeOfRSeqCriticalSection = 32
|
||||
|
||||
// SizeOfRSeqSignature is the size of the signature immediately
|
||||
// preceding RSeqCriticalSection.Abort.
|
||||
SizeOfRSeqSignature = 4
|
||||
)
|
||||
|
||||
// Special values for RSeq.CPUID, defined in include/uapi/linux/rseq.h.
|
||||
const (
|
||||
// RSEQ_CPU_ID_UNINITIALIZED indicates that this thread has not
|
||||
// performed rseq initialization.
|
||||
RSEQ_CPU_ID_UNINITIALIZED = ^uint32(0) // -1
|
||||
|
||||
// RSEQ_CPU_ID_REGISTRATION_FAILED indicates that rseq initialization
|
||||
// failed.
|
||||
RSEQ_CPU_ID_REGISTRATION_FAILED = ^uint32(1) // -2
|
||||
)
|
||||
|
||||
// RSeq is the thread-local restartable sequences config/status. It
|
||||
// is equivalent to struct rseq, defined in include/uapi/linux/rseq.h.
|
||||
//
|
||||
// In userspace, this structure is always aligned to 32 bytes.
|
||||
type RSeq struct {
|
||||
// CPUIDStart contains the current CPU ID if rseq is initialized.
|
||||
//
|
||||
// This field should only be read by the thread which registered this
|
||||
// structure, and must be read atomically.
|
||||
CPUIDStart uint32
|
||||
|
||||
// CPUID contains the current CPU ID or one of the CPU ID special
|
||||
// values defined above.
|
||||
//
|
||||
// This field should only be read by the thread which registered this
|
||||
// structure, and must be read atomically.
|
||||
CPUID uint32
|
||||
|
||||
// RSeqCriticalSection is a pointer to the current RSeqCriticalSection
|
||||
// block, or NULL. It is reset to NULL by the kernel on restart or
|
||||
// non-restarting preempt/signal.
|
||||
//
|
||||
// This field should only be written by the thread which registered
|
||||
// this structure, and must be written atomically.
|
||||
RSeqCriticalSection uint64
|
||||
|
||||
// Flags are the critical section flags that apply to all critical
|
||||
// sections on this thread, defined above.
|
||||
Flags uint32
|
||||
}
|
||||
|
||||
const (
|
||||
// SizeOfRSeq is the size of RSeq.
|
||||
//
|
||||
// Note that RSeq is naively 24 bytes. However, it has 32-byte
|
||||
// alignment, which in C increases sizeof to 32. That is the size that
|
||||
// the Linux kernel uses.
|
||||
SizeOfRSeq = 32
|
||||
|
||||
// AlignOfRSeq is the standard alignment of RSeq.
|
||||
AlignOfRSeq = 32
|
||||
|
||||
// OffsetOfRSeqCriticalSection is the offset of RSeqCriticalSection in RSeq.
|
||||
OffsetOfRSeqCriticalSection = 8
|
||||
)
|
||||
@@ -125,9 +125,9 @@ type Context interface {
|
||||
// SetTLS sets the current TLS pointer. Returns false if value is invalid.
|
||||
SetTLS(value uintptr) bool
|
||||
|
||||
// SetRSEQInterruptedIP sets the register that contains the old IP when a
|
||||
// restartable sequence is interrupted.
|
||||
SetRSEQInterruptedIP(value uintptr)
|
||||
// SetOldRSeqInterruptedIP sets the register that contains the old IP
|
||||
// when an "old rseq" restartable sequence is interrupted.
|
||||
SetOldRSeqInterruptedIP(value uintptr)
|
||||
|
||||
// StateData returns a pointer to underlying architecture state.
|
||||
StateData() *State
|
||||
|
||||
@@ -174,8 +174,8 @@ func (c *context64) SetTLS(value uintptr) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetRSEQInterruptedIP implements Context.SetRSEQInterruptedIP.
|
||||
func (c *context64) SetRSEQInterruptedIP(value uintptr) {
|
||||
// SetOldRSeqInterruptedIP implements Context.SetOldRSeqInterruptedIP.
|
||||
func (c *context64) SetOldRSeqInterruptedIP(value uintptr) {
|
||||
c.Regs.R10 = uint64(value)
|
||||
}
|
||||
|
||||
|
||||
+339
-54
@@ -15,17 +15,29 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/hostcpu"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// Restartable sequences, as described in https://lwn.net/Articles/650333/.
|
||||
// Restartable sequences.
|
||||
//
|
||||
// We support two different APIs for restartable sequences.
|
||||
//
|
||||
// 1. The upstream interface added in v4.18.
|
||||
// 2. The interface described in https://lwn.net/Articles/650333/.
|
||||
//
|
||||
// Throughout this file and other parts of the kernel, the latter is referred
|
||||
// to as "old rseq". This interface was never merged upstream, but is supported
|
||||
// for a limited set of applications that use it regardless.
|
||||
|
||||
// RSEQCriticalRegion describes a restartable sequence critical region.
|
||||
// OldRSeqCriticalRegion describes an old rseq critical region.
|
||||
//
|
||||
// +stateify savable
|
||||
type RSEQCriticalRegion struct {
|
||||
type OldRSeqCriticalRegion struct {
|
||||
// When a task in this thread group has its CPU preempted (as defined by
|
||||
// platform.ErrContextCPUPreempted) or has a signal delivered to an
|
||||
// application handler while its instruction pointer is in CriticalSection,
|
||||
@@ -35,86 +47,359 @@ type RSEQCriticalRegion struct {
|
||||
Restart usermem.Addr
|
||||
}
|
||||
|
||||
// RSEQAvailable returns true if t supports restartable sequences.
|
||||
func (t *Task) RSEQAvailable() bool {
|
||||
// RSeqAvailable returns true if t supports (old and new) restartable sequences.
|
||||
func (t *Task) RSeqAvailable() bool {
|
||||
return t.k.useHostCores && t.k.Platform.DetectsCPUPreemption()
|
||||
}
|
||||
|
||||
// RSEQCriticalRegion returns a copy of t's thread group's current restartable
|
||||
// sequence.
|
||||
func (t *Task) RSEQCriticalRegion() RSEQCriticalRegion {
|
||||
return *t.tg.rscr.Load().(*RSEQCriticalRegion)
|
||||
}
|
||||
|
||||
// SetRSEQCriticalRegion replaces t's thread group's restartable sequence.
|
||||
// SetRSeq registers addr as this thread's rseq structure.
|
||||
//
|
||||
// Preconditions: t.RSEQAvailable() == true.
|
||||
func (t *Task) SetRSEQCriticalRegion(rscr RSEQCriticalRegion) error {
|
||||
// These checks are somewhat more lenient than in Linux, which (bizarrely)
|
||||
// requires rscr.CriticalSection to be non-empty and rscr.Restart to be
|
||||
// outside of rscr.CriticalSection, even if rscr.CriticalSection.Start == 0
|
||||
// (which disables the critical region).
|
||||
if rscr.CriticalSection.Start == 0 {
|
||||
rscr.CriticalSection.End = 0
|
||||
rscr.Restart = 0
|
||||
t.tg.rscr.Store(&rscr)
|
||||
return nil
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) SetRSeq(addr usermem.Addr, length, signature uint32) error {
|
||||
if t.rseqAddr != 0 {
|
||||
if t.rseqAddr != addr {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if t.rseqSignature != signature {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
return syserror.EBUSY
|
||||
}
|
||||
if rscr.CriticalSection.Start >= rscr.CriticalSection.End {
|
||||
|
||||
// rseq must be aligned and correctly sized.
|
||||
if addr&(linux.AlignOfRSeq-1) != 0 {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if rscr.CriticalSection.Contains(rscr.Restart) {
|
||||
if length != linux.SizeOfRSeq {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
// TODO(jamieliu): check that rscr.CriticalSection and rscr.Restart are in
|
||||
// the application address range, for consistency with Linux
|
||||
t.tg.rscr.Store(&rscr)
|
||||
if _, ok := t.MemoryManager().CheckIORange(addr, linux.SizeOfRSeq); !ok {
|
||||
return syserror.EFAULT
|
||||
}
|
||||
|
||||
t.rseqAddr = addr
|
||||
t.rseqSignature = signature
|
||||
|
||||
// Initialize the CPUID.
|
||||
//
|
||||
// Linux implicitly does this on return from userspace, where failure
|
||||
// would cause SIGSEGV.
|
||||
if err := t.rseqUpdateCPU(); err != nil {
|
||||
t.rseqAddr = 0
|
||||
t.rseqSignature = 0
|
||||
|
||||
t.Debugf("Failed to copy CPU to %#x for rseq: %v", t.rseqAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return syserror.EFAULT
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RSEQCPUAddr returns the address that RSEQ will keep updated with t's CPU
|
||||
// number.
|
||||
// ClearRSeq unregisters addr as this thread's rseq structure.
|
||||
//
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) RSEQCPUAddr() usermem.Addr {
|
||||
return t.rseqCPUAddr
|
||||
func (t *Task) ClearRSeq(addr usermem.Addr, length, signature uint32) error {
|
||||
if t.rseqAddr == 0 {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if t.rseqAddr != addr {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if length != linux.SizeOfRSeq {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if t.rseqSignature != signature {
|
||||
return syserror.EPERM
|
||||
}
|
||||
|
||||
if err := t.rseqClearCPU(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.rseqAddr = 0
|
||||
t.rseqSignature = 0
|
||||
|
||||
if t.oldRSeqCPUAddr == 0 {
|
||||
// rseqCPU no longer needed.
|
||||
t.rseqCPU = -1
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetRSEQCPUAddr replaces the address that RSEQ will keep updated with t's CPU
|
||||
// number.
|
||||
// OldRSeqCriticalRegion returns a copy of t's thread group's current
|
||||
// old restartable sequence.
|
||||
func (t *Task) OldRSeqCriticalRegion() OldRSeqCriticalRegion {
|
||||
return *t.tg.oldRSeqCritical.Load().(*OldRSeqCriticalRegion)
|
||||
}
|
||||
|
||||
// SetOldRSeqCriticalRegion replaces t's thread group's old restartable
|
||||
// sequence.
|
||||
//
|
||||
// Preconditions: t.RSEQAvailable() == true. The caller must be running on the
|
||||
// Preconditions: t.RSeqAvailable() == true.
|
||||
func (t *Task) SetOldRSeqCriticalRegion(r OldRSeqCriticalRegion) error {
|
||||
// These checks are somewhat more lenient than in Linux, which (bizarrely)
|
||||
// requires r.CriticalSection to be non-empty and r.Restart to be
|
||||
// outside of r.CriticalSection, even if r.CriticalSection.Start == 0
|
||||
// (which disables the critical region).
|
||||
if r.CriticalSection.Start == 0 {
|
||||
r.CriticalSection.End = 0
|
||||
r.Restart = 0
|
||||
t.tg.oldRSeqCritical.Store(&r)
|
||||
return nil
|
||||
}
|
||||
if r.CriticalSection.Start >= r.CriticalSection.End {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
if r.CriticalSection.Contains(r.Restart) {
|
||||
return syserror.EINVAL
|
||||
}
|
||||
// TODO(jamieliu): check that r.CriticalSection and r.Restart are in
|
||||
// the application address range, for consistency with Linux.
|
||||
t.tg.oldRSeqCritical.Store(&r)
|
||||
return nil
|
||||
}
|
||||
|
||||
// OldRSeqCPUAddr returns the address that old rseq will keep updated with t's
|
||||
// CPU number.
|
||||
//
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) OldRSeqCPUAddr() usermem.Addr {
|
||||
return t.oldRSeqCPUAddr
|
||||
}
|
||||
|
||||
// SetOldRSeqCPUAddr replaces the address that old rseq will keep updated with
|
||||
// t's CPU number.
|
||||
//
|
||||
// Preconditions: t.RSeqAvailable() == true. The caller must be running on the
|
||||
// task goroutine. t's AddressSpace must be active.
|
||||
func (t *Task) SetRSEQCPUAddr(addr usermem.Addr) error {
|
||||
t.rseqCPUAddr = addr
|
||||
if addr != 0 {
|
||||
t.rseqCPU = int32(hostcpu.GetCPU())
|
||||
if err := t.rseqCopyOutCPU(); err != nil {
|
||||
t.rseqCPUAddr = 0
|
||||
t.rseqCPU = -1
|
||||
return syserror.EINVAL // yes, EINVAL, not err or EFAULT
|
||||
}
|
||||
} else {
|
||||
t.rseqCPU = -1
|
||||
func (t *Task) SetOldRSeqCPUAddr(addr usermem.Addr) error {
|
||||
t.oldRSeqCPUAddr = addr
|
||||
|
||||
// Check that addr is writable.
|
||||
//
|
||||
// N.B. rseqUpdateCPU may fail on a bad t.rseqAddr as well. That's
|
||||
// unfortunate, but unlikely in a correct program.
|
||||
if err := t.rseqUpdateCPU(); err != nil {
|
||||
t.oldRSeqCPUAddr = 0
|
||||
return syserror.EINVAL // yes, EINVAL, not err or EFAULT
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine. t's
|
||||
// AddressSpace must be active.
|
||||
func (t *Task) rseqCopyOutCPU() error {
|
||||
func (t *Task) rseqUpdateCPU() error {
|
||||
if t.rseqAddr == 0 && t.oldRSeqCPUAddr == 0 {
|
||||
t.rseqCPU = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
t.rseqCPU = int32(hostcpu.GetCPU())
|
||||
|
||||
// Update both CPUs, even if one fails.
|
||||
rerr := t.rseqCopyOutCPU()
|
||||
oerr := t.oldRSeqCopyOutCPU()
|
||||
|
||||
if rerr != nil {
|
||||
return rerr
|
||||
}
|
||||
return oerr
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine. t's
|
||||
// AddressSpace must be active.
|
||||
func (t *Task) oldRSeqCopyOutCPU() error {
|
||||
if t.oldRSeqCPUAddr == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := t.CopyScratchBuffer(4)
|
||||
usermem.ByteOrder.PutUint32(buf, uint32(t.rseqCPU))
|
||||
_, err := t.CopyOutBytes(t.rseqCPUAddr, buf)
|
||||
_, err := t.CopyOutBytes(t.oldRSeqCPUAddr, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine. t's
|
||||
// AddressSpace must be active.
|
||||
func (t *Task) rseqCopyOutCPU() error {
|
||||
if t.rseqAddr == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := t.CopyScratchBuffer(8)
|
||||
// CPUIDStart and CPUID are the first two fields in linux.RSeq.
|
||||
usermem.ByteOrder.PutUint32(buf, uint32(t.rseqCPU)) // CPUIDStart
|
||||
usermem.ByteOrder.PutUint32(buf[4:], uint32(t.rseqCPU)) // CPUID
|
||||
// N.B. This write is not atomic, but since this occurs on the task
|
||||
// goroutine then as long as userspace uses a single-instruction read
|
||||
// it can't see an invalid value.
|
||||
_, err := t.CopyOutBytes(t.rseqAddr, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine. t's
|
||||
// AddressSpace must be active.
|
||||
func (t *Task) rseqClearCPU() error {
|
||||
buf := t.CopyScratchBuffer(8)
|
||||
// CPUIDStart and CPUID are the first two fields in linux.RSeq.
|
||||
usermem.ByteOrder.PutUint32(buf, 0) // CPUIDStart
|
||||
usermem.ByteOrder.PutUint32(buf[4:], linux.RSEQ_CPU_ID_UNINITIALIZED) // CPUID
|
||||
// N.B. This write is not atomic, but since this occurs on the task
|
||||
// goroutine then as long as userspace uses a single-instruction read
|
||||
// it can't see an invalid value.
|
||||
_, err := t.CopyOutBytes(t.rseqAddr, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// rseqAddrInterrupt checks if IP is in a critical section, and aborts if so.
|
||||
//
|
||||
// This is a bit complex since both the RSeq and RSeqCriticalSection structs
|
||||
// are stored in userspace. So we must:
|
||||
//
|
||||
// 1. Copy in the address of RSeqCriticalSection from RSeq.
|
||||
// 2. Copy in RSeqCriticalSection itself.
|
||||
// 3. Validate critical section struct version, address range, abort address.
|
||||
// 4. Validate the abort signature (4 bytes preceding abort IP match expected
|
||||
// signature).
|
||||
// 5. Clear address of RSeqCriticalSection from RSeq.
|
||||
// 6. Finally, conditionally abort.
|
||||
//
|
||||
// See kernel/rseq.c:rseq_ip_fixup for reference.
|
||||
//
|
||||
// Preconditions: The caller must be running on the task goroutine. t's
|
||||
// AddressSpace must be active.
|
||||
func (t *Task) rseqAddrInterrupt() {
|
||||
if t.rseqAddr == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
critAddrAddr, ok := t.rseqAddr.AddLength(linux.OffsetOfRSeqCriticalSection)
|
||||
if !ok {
|
||||
// SetRSeq should validate this.
|
||||
panic(fmt.Sprintf("t.rseqAddr (%#x) not large enough", t.rseqAddr))
|
||||
}
|
||||
|
||||
if t.Arch().Width() != 8 {
|
||||
// We only handle 64-bit for now.
|
||||
t.Debugf("Only 64-bit rseq supported.")
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
buf := t.CopyScratchBuffer(8)
|
||||
if _, err := t.CopyInBytes(critAddrAddr, buf); err != nil {
|
||||
t.Debugf("Failed to copy critical section address from %#x for rseq: %v", critAddrAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
critAddr := usermem.Addr(usermem.ByteOrder.Uint64(buf))
|
||||
if critAddr == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
buf = t.CopyScratchBuffer(linux.SizeOfRSeqCriticalSection)
|
||||
if _, err := t.CopyInBytes(critAddr, buf); err != nil {
|
||||
t.Debugf("Failed to copy critical section from %#x for rseq: %v", critAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
// Manually marshal RSeqCriticalSection as this is in the hot path when
|
||||
// rseq is enabled. It must be as fast as possible.
|
||||
//
|
||||
// TODO(b/130243041): Replace with go_marshal.
|
||||
cs := linux.RSeqCriticalSection{
|
||||
Version: usermem.ByteOrder.Uint32(buf[0:4]),
|
||||
Flags: usermem.ByteOrder.Uint32(buf[4:8]),
|
||||
Start: usermem.ByteOrder.Uint64(buf[8:16]),
|
||||
PostCommitOffset: usermem.ByteOrder.Uint64(buf[16:24]),
|
||||
Abort: usermem.ByteOrder.Uint64(buf[24:32]),
|
||||
}
|
||||
|
||||
if cs.Version != 0 {
|
||||
t.Debugf("Unknown version in %+v", cs)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
start := usermem.Addr(cs.Start)
|
||||
critRange, ok := start.ToRange(cs.PostCommitOffset)
|
||||
if !ok {
|
||||
t.Debugf("Invalid start and offset in %+v", cs)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
abort := usermem.Addr(cs.Abort)
|
||||
if critRange.Contains(abort) {
|
||||
t.Debugf("Abort in critical section in %+v", cs)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
// Verify signature.
|
||||
sigAddr := abort - linux.SizeOfRSeqSignature
|
||||
|
||||
buf = t.CopyScratchBuffer(linux.SizeOfRSeqSignature)
|
||||
if _, err := t.CopyInBytes(sigAddr, buf); err != nil {
|
||||
t.Debugf("Failed to copy critical section signature from %#x for rseq: %v", sigAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
sig := usermem.ByteOrder.Uint32(buf)
|
||||
if sig != t.rseqSignature {
|
||||
t.Debugf("Mismatched rseq signature %d != %d", sig, t.rseqSignature)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
// Clear the critical section address.
|
||||
//
|
||||
// NOTE(b/143949567): We don't support any rseq flags, so we always
|
||||
// restart if we are in the critical section, and thus *always* clear
|
||||
// critAddrAddr.
|
||||
if _, err := t.MemoryManager().ZeroOut(t, critAddrAddr, int64(t.Arch().Width()), usermem.IOOpts{
|
||||
AddressSpaceActive: true,
|
||||
}); err != nil {
|
||||
t.Debugf("Failed to clear critical section address from %#x for rseq: %v", critAddrAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false /* unconditional */)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
return
|
||||
}
|
||||
|
||||
// Finally we can actually decide whether or not to restart.
|
||||
if !critRange.Contains(usermem.Addr(t.Arch().IP())) {
|
||||
return
|
||||
}
|
||||
|
||||
t.Arch().SetIP(uintptr(cs.Abort))
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) oldRSeqInterrupt() {
|
||||
r := t.tg.oldRSeqCritical.Load().(*OldRSeqCriticalRegion)
|
||||
if ip := t.Arch().IP(); r.CriticalSection.Contains(usermem.Addr(ip)) {
|
||||
t.Debugf("Interrupted rseq critical section at %#x; restarting at %#x", ip, r.Restart)
|
||||
t.Arch().SetIP(uintptr(r.Restart))
|
||||
t.Arch().SetOldRSeqInterruptedIP(ip)
|
||||
}
|
||||
}
|
||||
|
||||
// Preconditions: The caller must be running on the task goroutine.
|
||||
func (t *Task) rseqInterrupt() {
|
||||
rscr := t.tg.rscr.Load().(*RSEQCriticalRegion)
|
||||
if ip := t.Arch().IP(); rscr.CriticalSection.Contains(usermem.Addr(ip)) {
|
||||
t.Debugf("Interrupted RSEQ critical section at %#x; restarting at %#x", ip, rscr.Restart)
|
||||
t.Arch().SetIP(uintptr(rscr.Restart))
|
||||
t.Arch().SetRSEQInterruptedIP(ip)
|
||||
}
|
||||
t.rseqAddrInterrupt()
|
||||
t.oldRSeqInterrupt()
|
||||
}
|
||||
|
||||
@@ -489,18 +489,43 @@ type Task struct {
|
||||
// netns is protected by mu. netns is owned by the task goroutine.
|
||||
netns bool
|
||||
|
||||
// If rseqPreempted is true, before the next call to p.Switch(), interrupt
|
||||
// RSEQ critical regions as defined by tg.rseq and write the task
|
||||
// goroutine's CPU number to rseqCPUAddr. rseqCPU is the last CPU number
|
||||
// written to rseqCPUAddr.
|
||||
// If rseqPreempted is true, before the next call to p.Switch(),
|
||||
// interrupt rseq critical regions as defined by rseqAddr and
|
||||
// tg.oldRSeqCritical and write the task goroutine's CPU number to
|
||||
// rseqAddr/oldRSeqCPUAddr.
|
||||
//
|
||||
// If rseqCPUAddr is 0, rseqCPU is -1.
|
||||
// We support two ABIs for restartable sequences:
|
||||
//
|
||||
// rseqCPUAddr, rseqCPU, and rseqPreempted are exclusive to the task
|
||||
// goroutine.
|
||||
// 1. The upstream interface added in v4.18,
|
||||
// 2. An "old" interface never merged upstream. In the implementation,
|
||||
// this is referred to as "old rseq".
|
||||
//
|
||||
// rseqPreempted is exclusive to the task goroutine.
|
||||
rseqPreempted bool `state:"nosave"`
|
||||
rseqCPUAddr usermem.Addr
|
||||
rseqCPU int32
|
||||
|
||||
// rseqCPU is the last CPU number written to rseqAddr/oldRSeqCPUAddr.
|
||||
//
|
||||
// If rseq is unused, rseqCPU is -1 for convenient use in
|
||||
// platform.Context.Switch.
|
||||
//
|
||||
// rseqCPU is exclusive to the task goroutine.
|
||||
rseqCPU int32
|
||||
|
||||
// oldRSeqCPUAddr is a pointer to the userspace old rseq CPU variable.
|
||||
//
|
||||
// oldRSeqCPUAddr is exclusive to the task goroutine.
|
||||
oldRSeqCPUAddr usermem.Addr
|
||||
|
||||
// rseqAddr is a pointer to the userspace linux.RSeq structure.
|
||||
//
|
||||
// rseqAddr is exclusive to the task goroutine.
|
||||
rseqAddr usermem.Addr
|
||||
|
||||
// rseqSignature is the signature that the rseq abort IP must be signed
|
||||
// with.
|
||||
//
|
||||
// rseqSignature is exclusive to the task goroutine.
|
||||
rseqSignature uint32
|
||||
|
||||
// copyScratchBuffer is a buffer available to CopyIn/CopyOut
|
||||
// implementations that require an intermediate buffer to copy data
|
||||
|
||||
@@ -236,7 +236,10 @@ func (t *Task) Clone(opts *CloneOptions) (ThreadID, *SyscallControl, error) {
|
||||
} else if opts.NewPIDNamespace {
|
||||
pidns = pidns.NewChild(userns)
|
||||
}
|
||||
|
||||
tg := t.tg
|
||||
rseqAddr := usermem.Addr(0)
|
||||
rseqSignature := uint32(0)
|
||||
if opts.NewThreadGroup {
|
||||
tg.mounts.IncRef()
|
||||
sh := t.tg.signalHandlers
|
||||
@@ -244,6 +247,8 @@ func (t *Task) Clone(opts *CloneOptions) (ThreadID, *SyscallControl, error) {
|
||||
sh = sh.Fork()
|
||||
}
|
||||
tg = t.k.NewThreadGroup(tg.mounts, pidns, sh, opts.TerminationSignal, tg.limits.GetCopy())
|
||||
rseqAddr = t.rseqAddr
|
||||
rseqSignature = t.rseqSignature
|
||||
}
|
||||
|
||||
cfg := &TaskConfig{
|
||||
@@ -260,6 +265,8 @@ func (t *Task) Clone(opts *CloneOptions) (ThreadID, *SyscallControl, error) {
|
||||
UTSNamespace: utsns,
|
||||
IPCNamespace: ipcns,
|
||||
AbstractSocketNamespace: t.abstractSockets,
|
||||
RSeqAddr: rseqAddr,
|
||||
RSeqSignature: rseqSignature,
|
||||
ContainerID: t.ContainerID(),
|
||||
}
|
||||
if opts.NewThreadGroup {
|
||||
|
||||
@@ -190,9 +190,11 @@ func (r *runSyscallAfterExecStop) execute(t *Task) taskRunState {
|
||||
t.updateRSSLocked()
|
||||
// Restartable sequence state is discarded.
|
||||
t.rseqPreempted = false
|
||||
t.rseqCPUAddr = 0
|
||||
t.rseqCPU = -1
|
||||
t.tg.rscr.Store(&RSEQCriticalRegion{})
|
||||
t.rseqAddr = 0
|
||||
t.rseqSignature = 0
|
||||
t.oldRSeqCPUAddr = 0
|
||||
t.tg.oldRSeqCritical.Store(&OldRSeqCriticalRegion{})
|
||||
t.tg.pidns.owner.mu.Unlock()
|
||||
|
||||
// Remove FDs with the CloseOnExec flag set.
|
||||
|
||||
@@ -169,12 +169,22 @@ func (*runApp) execute(t *Task) taskRunState {
|
||||
// Apply restartable sequences.
|
||||
if t.rseqPreempted {
|
||||
t.rseqPreempted = false
|
||||
if t.rseqCPUAddr != 0 {
|
||||
if t.rseqAddr != 0 || t.oldRSeqCPUAddr != 0 {
|
||||
// Linux writes the CPU on every preemption. We only do
|
||||
// so if it changed. Thus we may delay delivery of
|
||||
// SIGSEGV if rseqAddr/oldRSeqCPUAddr is invalid.
|
||||
cpu := int32(hostcpu.GetCPU())
|
||||
if t.rseqCPU != cpu {
|
||||
t.rseqCPU = cpu
|
||||
if err := t.rseqCopyOutCPU(); err != nil {
|
||||
t.Warningf("Failed to copy CPU to %#x for RSEQ: %v", t.rseqCPUAddr, err)
|
||||
t.Debugf("Failed to copy CPU to %#x for rseq: %v", t.rseqAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
// Re-enter the task run loop for signal delivery.
|
||||
return (*runApp)(nil)
|
||||
}
|
||||
if err := t.oldRSeqCopyOutCPU(); err != nil {
|
||||
t.Debugf("Failed to copy CPU to %#x for old rseq: %v", t.oldRSeqCPUAddr, err)
|
||||
t.forceSignal(linux.SIGSEGV, false)
|
||||
t.SendSignal(SignalInfoPriv(linux.SIGSEGV))
|
||||
// Re-enter the task run loop for signal delivery.
|
||||
@@ -320,7 +330,7 @@ func (*runApp) execute(t *Task) taskRunState {
|
||||
return (*runApp)(nil)
|
||||
|
||||
case platform.ErrContextCPUPreempted:
|
||||
// Ensure that RSEQ critical sections are interrupted and per-thread
|
||||
// Ensure that rseq critical sections are interrupted and per-thread
|
||||
// CPU values are updated before the next platform.Context.Switch().
|
||||
t.rseqPreempted = true
|
||||
return (*runApp)(nil)
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/futex"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/sched"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usage"
|
||||
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
@@ -79,6 +80,13 @@ type TaskConfig struct {
|
||||
// AbstractSocketNamespace is the AbstractSocketNamespace of the new task.
|
||||
AbstractSocketNamespace *AbstractSocketNamespace
|
||||
|
||||
// RSeqAddr is a pointer to the the userspace linux.RSeq structure.
|
||||
RSeqAddr usermem.Addr
|
||||
|
||||
// RSeqSignature is the signature that the rseq abort IP must be signed
|
||||
// with.
|
||||
RSeqSignature uint32
|
||||
|
||||
// ContainerID is the container the new task belongs to.
|
||||
ContainerID string
|
||||
}
|
||||
@@ -126,6 +134,8 @@ func (ts *TaskSet) newTask(cfg *TaskConfig) (*Task, error) {
|
||||
ipcns: cfg.IPCNamespace,
|
||||
abstractSockets: cfg.AbstractSocketNamespace,
|
||||
rseqCPU: -1,
|
||||
rseqAddr: cfg.RSeqAddr,
|
||||
rseqSignature: cfg.RSeqSignature,
|
||||
futexWaiter: futex.NewWaiter(),
|
||||
containerID: cfg.ContainerID,
|
||||
}
|
||||
|
||||
@@ -238,8 +238,8 @@ type ThreadGroup struct {
|
||||
// execed is protected by the TaskSet mutex.
|
||||
execed bool
|
||||
|
||||
// rscr is the thread group's RSEQ critical region.
|
||||
rscr atomic.Value `state:".(*RSEQCriticalRegion)"`
|
||||
// oldRSeqCritical is the thread group's old rseq critical region.
|
||||
oldRSeqCritical atomic.Value `state:".(*OldRSeqCriticalRegion)"`
|
||||
|
||||
// mounts is the thread group's mount namespace. This does not really
|
||||
// correspond to a "mount namespace" in Linux, but is more like a
|
||||
@@ -273,18 +273,18 @@ func (k *Kernel) NewThreadGroup(mntns *fs.MountNamespace, pidns *PIDNamespace, s
|
||||
}
|
||||
tg.itimerRealTimer = ktime.NewTimer(k.monotonicClock, &itimerRealListener{tg: tg})
|
||||
tg.timers = make(map[linux.TimerID]*IntervalTimer)
|
||||
tg.rscr.Store(&RSEQCriticalRegion{})
|
||||
tg.oldRSeqCritical.Store(&OldRSeqCriticalRegion{})
|
||||
return tg
|
||||
}
|
||||
|
||||
// saveRscr is invoked by stateify.
|
||||
func (tg *ThreadGroup) saveRscr() *RSEQCriticalRegion {
|
||||
return tg.rscr.Load().(*RSEQCriticalRegion)
|
||||
// saveOldRSeqCritical is invoked by stateify.
|
||||
func (tg *ThreadGroup) saveOldRSeqCritical() *OldRSeqCriticalRegion {
|
||||
return tg.oldRSeqCritical.Load().(*OldRSeqCriticalRegion)
|
||||
}
|
||||
|
||||
// loadRscr is invoked by stateify.
|
||||
func (tg *ThreadGroup) loadRscr(rscr *RSEQCriticalRegion) {
|
||||
tg.rscr.Store(rscr)
|
||||
// loadOldRSeqCritical is invoked by stateify.
|
||||
func (tg *ThreadGroup) loadOldRSeqCritical(r *OldRSeqCriticalRegion) {
|
||||
tg.oldRSeqCritical.Store(r)
|
||||
}
|
||||
|
||||
// SignalHandlers returns the signal handlers used by tg.
|
||||
|
||||
@@ -30,6 +30,7 @@ go_library(
|
||||
"sys_random.go",
|
||||
"sys_read.go",
|
||||
"sys_rlimit.go",
|
||||
"sys_rseq.go",
|
||||
"sys_rusage.go",
|
||||
"sys_sched.go",
|
||||
"sys_seccomp.go",
|
||||
|
||||
@@ -377,7 +377,7 @@ var AMD64 = &kernel.SyscallTable{
|
||||
331: syscalls.ErrorWithEvent("pkey_free", syserror.ENOSYS, "", nil),
|
||||
332: syscalls.Supported("statx", Statx),
|
||||
333: syscalls.ErrorWithEvent("io_pgetevents", syserror.ENOSYS, "", nil),
|
||||
334: syscalls.ErrorWithEvent("rseq", syserror.ENOSYS, "", nil),
|
||||
334: syscalls.PartiallySupported("rseq", RSeq, "Not supported on all platforms.", nil),
|
||||
|
||||
// Linux skips ahead to syscall 424 to sync numbers between arches.
|
||||
424: syscalls.ErrorWithEvent("pidfd_send_signal", syserror.ENOSYS, "", nil),
|
||||
|
||||
@@ -307,7 +307,7 @@ var ARM64 = &kernel.SyscallTable{
|
||||
290: syscalls.ErrorWithEvent("pkey_free", syserror.ENOSYS, "", nil),
|
||||
291: syscalls.Supported("statx", Statx),
|
||||
292: syscalls.ErrorWithEvent("io_pgetevents", syserror.ENOSYS, "", nil),
|
||||
293: syscalls.ErrorWithEvent("rseq", syserror.ENOSYS, "", nil),
|
||||
293: syscalls.PartiallySupported("rseq", RSeq, "Not supported on all platforms.", nil),
|
||||
|
||||
// Linux skips ahead to syscall 424 to sync numbers between arches.
|
||||
424: syscalls.ErrorWithEvent("pidfd_send_signal", syserror.ENOSYS, "", nil),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
package linux
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/sentry/arch"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// RSeq implements syscall rseq(2).
|
||||
func RSeq(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
||||
addr := args[0].Pointer()
|
||||
length := args[1].Uint()
|
||||
flags := args[2].Int()
|
||||
signature := args[3].Uint()
|
||||
|
||||
if !t.RSeqAvailable() {
|
||||
// Event for applications that want rseq on a configuration
|
||||
// that doesn't support them.
|
||||
t.Kernel().EmitUnimplementedEvent(t)
|
||||
return 0, nil, syserror.ENOSYS
|
||||
}
|
||||
|
||||
switch flags {
|
||||
case 0:
|
||||
// Register.
|
||||
return 0, nil, t.SetRSeq(addr, length, signature)
|
||||
case linux.RSEQ_FLAG_UNREGISTER:
|
||||
return 0, nil, t.ClearRSeq(addr, length, signature)
|
||||
default:
|
||||
// Unknown flag.
|
||||
return 0, nil, syserror.EINVAL
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user