From 44644d9c19499b9e32bedbf5ddca223e8f18002f Mon Sep 17 00:00:00 2001 From: Jamie Liu Date: Wed, 16 Feb 2022 17:21:14 -0800 Subject: [PATCH] Save/restore floating point state in amd64 signal frames. PiperOrigin-RevId: 429178859 --- pkg/sentry/arch/arch.go | 9 +- pkg/sentry/arch/arch_amd64.go | 12 +-- pkg/sentry/arch/arch_x86.go | 1 - pkg/sentry/arch/fpu/fpu_amd64.go | 94 ++++++++++------- pkg/sentry/arch/signal_amd64.go | 139 ++++++++++++++++--------- pkg/sentry/arch/signal_arm64.go | 5 +- pkg/sentry/kernel/task_signals.go | 4 +- test/syscalls/BUILD | 4 + test/syscalls/linux/BUILD | 16 +++ test/syscalls/linux/fpsig_mut_amd64.cc | 111 ++++++++++++++++++++ 10 files changed, 288 insertions(+), 107 deletions(-) create mode 100644 test/syscalls/linux/fpsig_mut_amd64.cc diff --git a/pkg/sentry/arch/arch.go b/pkg/sentry/arch/arch.go index 3f31ddd8a..b7d3cb5cb 100644 --- a/pkg/sentry/arch/arch.go +++ b/pkg/sentry/arch/arch.go @@ -148,7 +148,9 @@ type Context interface { // stack is not going to be used). // // sigset is the signal mask before entering the signal handler. - SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error + // + // featureSet is the application CPU feature set. + SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error // SignalRestore restores context after returning from a signal // handler. @@ -157,8 +159,11 @@ type Context interface { // // rt is true if SignalRestore is being entered from rt_sigreturn and // false if SignalRestore is being entered from sigreturn. + // + // featureSet is the application CPU feature set. + // // SignalRestore returns the thread's new signal mask. - SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error) + SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error) // SingleStep returns true if single stepping is enabled. SingleStep() bool diff --git a/pkg/sentry/arch/arch_amd64.go b/pkg/sentry/arch/arch_amd64.go index b2e4398dd..e7e2e5015 100644 --- a/pkg/sentry/arch/arch_amd64.go +++ b/pkg/sentry/arch/arch_amd64.go @@ -106,7 +106,6 @@ const ( // +stateify savable type context64 struct { State - sigFPState []fpu.State // fpstate to be restored on sigreturn. } // Arch implements Context.Arch. @@ -114,14 +113,6 @@ func (c *context64) Arch() Arch { return AMD64 } -func (c *context64) copySigFPState() []fpu.State { - var sigfps []fpu.State - for _, s := range c.sigFPState { - sigfps = append(sigfps, s.Fork()) - } - return sigfps -} - func (c *context64) FloatingPointData() *fpu.State { return &c.State.fpState } @@ -129,8 +120,7 @@ func (c *context64) FloatingPointData() *fpu.State { // Fork returns an exact copy of this context. func (c *context64) Fork() Context { return &context64{ - State: c.State.Fork(), - sigFPState: c.copySigFPState(), + State: c.State.Fork(), } } diff --git a/pkg/sentry/arch/arch_x86.go b/pkg/sentry/arch/arch_x86.go index 35dda469a..7c1b1601c 100644 --- a/pkg/sentry/arch/arch_x86.go +++ b/pkg/sentry/arch/arch_x86.go @@ -397,7 +397,6 @@ func New(arch Arch) Context { State{ fpState: fpu.NewState(), }, - []fpu.State(nil), } } panic(fmt.Sprintf("unknown architecture %v", arch)) diff --git a/pkg/sentry/arch/fpu/fpu_amd64.go b/pkg/sentry/arch/fpu/fpu_amd64.go index 9bd9dc7c1..b3fb39e1b 100644 --- a/pkg/sentry/arch/fpu/fpu_amd64.go +++ b/pkg/sentry/arch/fpu/fpu_amd64.go @@ -63,6 +63,15 @@ func (s *State) Fork() State { return n } +// Reset resets s to its initial state. +func (s *State) Reset() { + f := *s + for i := range f { + f[i] = 0 + } + initX86FPState(&f[0], cpuid.HostFeatureSet().UseXsave()) +} + // ptraceFPRegsSize is the size in bytes of Linux's user_i387_struct, the type // manipulated by PTRACE_GETFPREGS and PTRACE_SETFPREGS on x86. Equivalently, // ptraceFPRegsSize is the size in bytes of the x86 FXSAVE area. @@ -107,11 +116,6 @@ const ( mxcsrMaskOffset = 28 ) -var ( - mxcsrMask uint32 - initMXCSRMask sync.Once -) - const ( // minXstateBytes is the minimum size in bytes of an x86 XSAVE area, equal // to the size of the XSAVE legacy area (512 bytes) plus the size of the @@ -142,28 +146,6 @@ const ( xsaveHeaderZeroedBytes = 64 - 8 ) -// sanitizeMXCSR coerces reserved bits in the MXCSR field of f to 0. ("FXRSTOR -// generates a general-protection fault (#GP) in response to an attempt to set -// any of the reserved bits of the MXCSR register." - Intel SDM Vol. 1, Section -// 10.5.1.2 "SSE State") -func sanitizeMXCSR(f State) { - mxcsr := hostarch.ByteOrder.Uint32(f[mxcsrOffset:]) - initMXCSRMask.Do(func() { - temp := State(alignedBytes(uint(ptraceFPRegsSize), 16)) - initX86FPState(&temp[0], false /* useXsave */) - mxcsrMask = hostarch.ByteOrder.Uint32(temp[mxcsrMaskOffset:]) - if mxcsrMask == 0 { - // "If the value of the MXCSR_MASK field is 00000000H, then the - // MXCSR_MASK value is the default value of 0000FFBFH." - Intel SDM - // Vol. 1, Section 11.6.6 "Guidelines for Writing to the MXCSR - // Register" - mxcsrMask = 0xffbf - } - }) - mxcsr &= mxcsrMask - hostarch.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr) -} - // PtraceGetXstateRegs implements ptrace(PTRACE_GETREGS, NT_X86_XSTATE) by // writing the floating point registers from this state to dst and returning the // number of bytes written, which must be less than or equal to maxlen. @@ -206,18 +188,56 @@ func (s *State) PtraceSetXstateRegs(src io.Reader, maxlen int, featureSet cpuid. if _, err := io.ReadFull(src, f); err != nil { return 0, err } + n := copy(*s, f) + s.SanitizeUser(featureSet) + return n, nil +} + +// SanitizeUser mutates s to ensure that restoring it is safe. +func (s *State) SanitizeUser(featureSet cpuid.FeatureSet) { + f := *s + // Force reserved bits in MXCSR to 0. This is consistent with Linux. - sanitizeMXCSR(State(f)) - // Users can't enable *more* XCR0 bits than what we, and the CPU, support. - xstateBV := hostarch.ByteOrder.Uint64(f[xstateBVOffset:]) - xstateBV &= featureSet.ValidXCR0Mask() - hostarch.ByteOrder.PutUint64(f[xstateBVOffset:], xstateBV) - // Force XCOMP_BV and reserved bytes in the XSAVE header to 0. - reserved := f[xsaveHeaderZeroedOffset : xsaveHeaderZeroedOffset+xsaveHeaderZeroedBytes] - for i := range reserved { - reserved[i] = 0 + sanitizeMXCSR(f) + + if len(f) >= minXstateBytes { + // Users can't enable *more* XCR0 bits than what we, and the CPU, support. + xstateBV := hostarch.ByteOrder.Uint64(f[xstateBVOffset:]) + xstateBV &= featureSet.ValidXCR0Mask() + hostarch.ByteOrder.PutUint64(f[xstateBVOffset:], xstateBV) + // Force XCOMP_BV and reserved bytes in the XSAVE header to 0. + reserved := f[xsaveHeaderZeroedOffset : xsaveHeaderZeroedOffset+xsaveHeaderZeroedBytes] + for i := range reserved { + reserved[i] = 0 + } } - return copy(*s, f), nil +} + +var ( + mxcsrMask uint32 + initMXCSRMask sync.Once +) + +// sanitizeMXCSR coerces reserved bits in the MXCSR field of f to 0. ("FXRSTOR +// generates a general-protection fault (#GP) in response to an attempt to set +// any of the reserved bits of the MXCSR register." - Intel SDM Vol. 1, Section +// 10.5.1.2 "SSE State") +func sanitizeMXCSR(f State) { + mxcsr := hostarch.ByteOrder.Uint32(f[mxcsrOffset:]) + initMXCSRMask.Do(func() { + temp := State(alignedBytes(uint(ptraceFPRegsSize), 16)) + initX86FPState(&temp[0], false /* useXsave */) + mxcsrMask = hostarch.ByteOrder.Uint32(temp[mxcsrMaskOffset:]) + if mxcsrMask == 0 { + // "If the value of the MXCSR_MASK field is 00000000H, then the + // MXCSR_MASK value is the default value of 0000FFBFH." - Intel SDM + // Vol. 1, Section 11.6.6 "Guidelines for Writing to the MXCSR + // Register" + mxcsrMask = 0xffbf + } + }) + mxcsr &= mxcsrMask + hostarch.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr) } // SetMXCSR sets the MXCSR control/status register in the state. diff --git a/pkg/sentry/arch/signal_amd64.go b/pkg/sentry/arch/signal_amd64.go index dbd4336f9..1c1b23af6 100644 --- a/pkg/sentry/arch/signal_amd64.go +++ b/pkg/sentry/arch/signal_amd64.go @@ -22,10 +22,11 @@ import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/context" + "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/hostarch" - "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/marshal/primitive" - "gvisor.dev/gvisor/pkg/sentry/arch/fpu" + "gvisor.dev/gvisor/pkg/usermem" ) // SignalContext64 is equivalent to struct sigcontext, the type passed as the @@ -59,7 +60,7 @@ type SignalContext64 struct { Trapno uint64 Oldmask linux.SignalSet Cr2 uint64 - // Pointer to a struct _fpstate. See b/33003106#comment8. + // Pointer to a struct _fpstate. Fpstate uint64 Reserved [8]uint64 } @@ -82,28 +83,38 @@ type UContext64 struct { Sigset linux.SignalSet } -// From Linux 'arch/x86/include/uapi/asm/sigcontext.h' the following is the -// size of the magic cookie at the end of the xsave frame. +// FPSoftwareFrame is equivalent to struct _fpx_sw_bytes, the data stored by +// Linux in bytes 464:511 of the fxsave/xsave frame. // -// NOTE(b/33003106#comment11): Currently we don't actually populate the fpstate -// on the signal stack. -const _FP_XSTATE_MAGIC2_SIZE = 4 - -func (c *context64) fpuFrameSize() (size int, useXsave bool) { - size = len(c.fpState) - if size > 512 { - // Make room for the magic cookie at the end of the xsave frame. - size += _FP_XSTATE_MAGIC2_SIZE - useXsave = true - } - return size, useXsave +// +marshal +type FPSoftwareFrame struct { + Magic1 uint32 + ExtendedSize uint32 + Xfeatures uint64 + XstateSize uint32 + Padding [7]uint32 } +// From Linux's arch/x86/include/uapi/asm/sigcontext.h. +const ( + // Value of FPSoftwareFrame.Magic1. + _FP_XSTATE_MAGIC1 = 0x46505853 + + // Value written to the 4 bytes inserted by Linux after the fxsave/xsave + // area in the signal frame. + _FP_XSTATE_MAGIC2 = 0x46505845 + _FP_XSTATE_MAGIC2_SIZE = 4 +) + +// From Linux's arch/x86/include/asm/fpu/types.h. +const ( + // xsave features that are always enabled in signal frame fpstate. + _XFEATURE_MASK_FPSSE = 0x3 +) + // SignalSetup implements Context.SignalSetup. (Compare to Linux's // arch/x86/kernel/signal.c:__setup_rt_frame().) -func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error { - sp := st.Bottom - +func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error { // "The 128-byte area beyond the location pointed to by %rsp is considered // to be reserved and shall not be modified by signal or interrupt // handlers. ... leaf functions may use this area for their entire stack @@ -112,23 +123,22 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig // // (But this doesn't apply if we're starting at the top of the signal // stack, in which case there is no following stack frame.) + sp := st.Bottom if !(alt.IsEnabled() && sp == alt.Top()) { sp -= 128 } // Allocate space for floating point state on the stack. - // - // This isn't strictly necessary because we don't actually populate - // the fpstate. However we do store the floating point state of the - // interrupted thread inside the sentry. Simply accounting for this - // space on the user stack naturally caps the amount of memory the - // sentry will allocate for this purpose. - fpSize, _ := c.fpuFrameSize() - sp = (sp - hostarch.Addr(fpSize)) & ^hostarch.Addr(63) + fpSize, fpAlign := featureSet.ExtendedStateSize() + if fpSize < 512 { + // We expect support for at least FXSAVE. + fpSize = 512 + } + fpSize += _FP_XSTATE_MAGIC2_SIZE + fpStart := (sp - hostarch.Addr(fpSize)) & ^hostarch.Addr(fpAlign-1) // Construct the UContext64 now since we need its size. uc := &UContext64{ - // No _UC_FP_XSTATE: see Fpstate above. // No _UC_STRICT_RESTORE_SS: we don't allow SS changes. Flags: _UC_SIGCONTEXT_SS, Stack: *alt, @@ -154,9 +164,13 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig Cs: uint16(c.Regs.Cs), Ss: uint16(c.Regs.Ss), Oldmask: sigset, + Fpstate: uint64(fpStart), }, Sigset: sigset, } + if featureSet.UseXsave() { + uc.Flags |= _UC_FP_XSTATE + } // TODO(gvisor.dev/issue/159): Set SignalContext64.Err, Trapno, and Cr2 // based on the fault that caused the signal. For now, leave Err and @@ -171,21 +185,46 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig ucSize := uc.SizeBytes() // st.Arch.Width() is for the restorer address. sizeof(siginfo) == 128. frameSize := int(st.Arch.Width()) + ucSize + 128 - frameBottom := (sp-hostarch.Addr(frameSize)) & ^hostarch.Addr(15) - 8 - sp = frameBottom + hostarch.Addr(frameSize) - st.Bottom = sp + frameStart := (fpStart-hostarch.Addr(frameSize)) & ^hostarch.Addr(15) - 8 + frameEnd := frameStart + hostarch.Addr(frameSize) // Prior to proceeding, figure out if the frame will exhaust the range // for the signal stack. This is not allowed, and should immediately // force signal delivery (reverting to the default handler). - if act.Flags&linux.SA_ONSTACK != 0 && alt.IsEnabled() && !alt.Contains(frameBottom) { + if act.Flags&linux.SA_ONSTACK != 0 && alt.IsEnabled() && !alt.Contains(frameStart) { return unix.EFAULT } + // Set up floating point state on the stack. Compare Linux's + // arch/x86/kernel/fpu/signal.c:copy_fpstate_to_sigframe(). + if _, err := st.IO.CopyOut(context.Background(), fpStart, c.fpState[:464], usermem.IOOpts{}); err != nil { + return err + } + fpsw := FPSoftwareFrame{ + Magic1: _FP_XSTATE_MAGIC1, + ExtendedSize: uint32(fpSize), + Xfeatures: _XFEATURE_MASK_FPSSE | featureSet.ValidXCR0Mask(), + XstateSize: uint32(fpSize) - _FP_XSTATE_MAGIC2_SIZE, + } + st.Bottom = fpStart + 512 + if _, err := fpsw.CopyOut(st, StackBottomMagic); err != nil { + return err + } + if len(c.fpState) > 512 { + if _, err := st.IO.CopyOut(context.Background(), fpStart+512, c.fpState[512:], usermem.IOOpts{}); err != nil { + return err + } + } + st.Bottom = fpStart + hostarch.Addr(fpSize) + if _, err := primitive.CopyUint32Out(st, StackBottomMagic, _FP_XSTATE_MAGIC2); err != nil { + return err + } + // Adjust the code. info.FixSignalCodeForUser() // Set up the stack frame. + st.Bottom = frameEnd if _, err := info.CopyOut(st, StackBottomMagic); err != nil { return err } @@ -212,23 +251,21 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig c.Regs.Rsi = uint64(infoAddr) c.Regs.Rdx = uint64(ucAddr) c.Regs.Rax = 0 + c.Regs.Eflags &^= eflagsDF | eflagsRF | eflagsTF c.Regs.Ds = userDS c.Regs.Es = userDS c.Regs.Cs = userCS c.Regs.Ss = userDS - // Save the thread's floating point state. - c.sigFPState = append(c.sigFPState, c.fpState) - - // Signal handler gets a clean floating point state. - c.fpState = fpu.NewState() + // Clear floating point registers. + c.fpState.Reset() return nil } // SignalRestore implements Context.SignalRestore. (Compare to Linux's // arch/x86/kernel/signal.c:sys_rt_sigreturn().) -func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error) { +func (c *context64) SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error) { // Copy out the stack frame. var uc UContext64 if _, err := uc.CopyIn(st, StackBottomMagic); err != nil { @@ -262,20 +299,18 @@ func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.Si // N.B. _UC_STRICT_RESTORE_SS not supported. c.Regs.Orig_rax = math.MaxUint64 - // Restore floating point state. - l := len(c.sigFPState) - if l > 0 { - c.fpState = c.sigFPState[l-1] - // NOTE(cl/133042258): State save requires that any slice - // elements from '[len:cap]' to be zero value. - c.sigFPState[l-1] = nil - c.sigFPState = c.sigFPState[0 : l-1] + // Restore floating point state. Compare Linux's + // arch/x86/kernel/fpu/signal.c:fpu__restore_sig(). + if uc.MContext.Fpstate == 0 { + c.fpState.Reset() } else { - // This might happen if sigreturn(2) calls are unbalanced with - // respect to signal handler entries. This is not expected so - // don't bother to do anything fancy with the floating point - // state. - log.Infof("sigreturn unable to restore application fpstate") + fpSize, _ := featureSet.ExtendedStateSize() + f := make([]byte, fpSize) + if _, err := st.IO.CopyIn(context.Background(), hostarch.Addr(uc.MContext.Fpstate), f, usermem.IOOpts{}); err != nil { + return 0, linux.SignalStack{}, err + } + copy(c.fpState, f) + c.fpState.SanitizeUser(featureSet) } return uc.Sigset, uc.Stack, nil diff --git a/pkg/sentry/arch/signal_arm64.go b/pkg/sentry/arch/signal_arm64.go index ee22ec512..694875308 100644 --- a/pkg/sentry/arch/signal_arm64.go +++ b/pkg/sentry/arch/signal_arm64.go @@ -20,6 +20,7 @@ package arch import ( "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/abi/linux" + "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/arch/fpu" @@ -73,7 +74,7 @@ type UContext64 struct { } // SignalSetup implements Context.SignalSetup. -func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet) error { +func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.SignalInfo, alt *linux.SignalStack, sigset linux.SignalSet, featureSet cpuid.FeatureSet) error { sp := st.Bottom // Construct the UContext64 now since we need its size. @@ -138,7 +139,7 @@ func (c *context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig } // SignalRestore implements Context.SignalRestore. -func (c *context64) SignalRestore(st *Stack, rt bool) (linux.SignalSet, linux.SignalStack, error) { +func (c *context64) SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSet) (linux.SignalSet, linux.SignalStack, error) { // Copy out the stack frame. var uc UContext64 if _, err := uc.CopyIn(st, StackBottomMagic); err != nil { diff --git a/pkg/sentry/kernel/task_signals.go b/pkg/sentry/kernel/task_signals.go index 3e8c0c89b..2e66e40ea 100644 --- a/pkg/sentry/kernel/task_signals.go +++ b/pkg/sentry/kernel/task_signals.go @@ -282,7 +282,7 @@ func (t *Task) deliverSignalToHandler(info *linux.SignalInfo, act linux.SigActio act.Restorer = mm.VDSOSigReturn() } - if err := t.Arch().SignalSetup(st, &act, info, &alt, mask); err != nil { + if err := t.Arch().SignalSetup(st, &act, info, &alt, mask, t.k.featureSet); err != nil { return err } t.p.FullStateChanged() @@ -304,7 +304,7 @@ var ctrlResume = &SyscallControl{ignoreReturn: true} // rt is true). func (t *Task) SignalReturn(rt bool) (*SyscallControl, error) { st := t.Stack() - sigset, alt, err := t.Arch().SignalRestore(st, rt) + sigset, alt, err := t.Arch().SignalRestore(st, rt, t.k.featureSet) if err != nil { return nil, err } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 5092fb059..2011ad221 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -193,6 +193,10 @@ syscall_test( test = "//test/syscalls/linux:fpsig_fork_test", ) +syscall_test( + test = "//test/syscalls/linux:fpsig_mut_test", +) + syscall_test( test = "//test/syscalls/linux:fpsig_nested_test", ) diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 8afa2b410..7f4d64632 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -888,6 +888,22 @@ cc_binary( ], ) +cc_binary( + name = "fpsig_mut_test", + testonly = 1, + srcs = select_arch( + amd64 = ["fpsig_mut_amd64.cc"], + arm64 = [], + ), + linkstatic = 1, + deps = [ + gtest, + "//test/util:test_main", + "//test/util:test_util", + "//test/util:thread_util", + ], +) + cc_binary( name = "fpsig_nested_test", testonly = 1, diff --git a/test/syscalls/linux/fpsig_mut_amd64.cc b/test/syscalls/linux/fpsig_mut_amd64.cc new file mode 100644 index 000000000..f76ce8a9b --- /dev/null +++ b/test/syscalls/linux/fpsig_mut_amd64.cc @@ -0,0 +1,111 @@ +// Copyright 2022 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. + +// This program verifies that application floating point state is visible in +// signal frames, and that changes to said state is visible after the signal +// handler returns. +#include +#include + +#include "gtest/gtest.h" +#include "test/util/test_util.h" +#include "test/util/thread_util.h" + +namespace gvisor { +namespace testing { + +namespace { + +#define GET_XMM(__var, __xmm) \ + asm volatile("movq %%" #__xmm ", %0" : "=r"(__var)) +#define SET_XMM(__var, __xmm) asm volatile("movq %0, %%" #__xmm : : "r"(__var)) + +int pid; +int tid; + +volatile uint64_t handlerxmm = ~0UL; +volatile uint64_t framexmm = ~0UL; + +constexpr uint64_t kOldFPRegValue = 0xdeadbeeffacefeed; +constexpr uint64_t kNewFPRegValue = 0xfacefeedbaad1dea; + +void sigusr1(int s, siginfo_t* siginfo, void* _uc) { + uint64_t val = SIGUSR1; + + // Record the value of %xmm0 on entry and then clobber it. + GET_XMM(handlerxmm, xmm0); + SET_XMM(val, xmm0); + + // Record the value of %xmm0 stored in _uc and then replace it. + ucontext_t* uc = reinterpret_cast(_uc); + auto* uc_xmm0 = &uc->uc_mcontext.fpregs->_xmm[0]; + framexmm = (static_cast(uc_xmm0->element[1]) << 32) | + static_cast(uc_xmm0->element[0]); + uc_xmm0->element[1] = static_cast(kNewFPRegValue >> 32); + uc_xmm0->element[0] = static_cast(kNewFPRegValue); +} + +TEST(FPSigTest, StateInFrame) { + pid = getpid(); + tid = gettid(); + + struct sigaction sa = {}; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = sigusr1; + ASSERT_THAT(sigaction(SIGUSR1, &sa, nullptr), SyscallSucceeds()); + + // The amd64 ABI specifies that the XMM register set is caller-saved. This + // implies that if there is any function call between SET_XMM and GET_XMM the + // compiler might save/restore xmm0 implicitly. This defeats the entire + // purpose of the test which is to verify that fpstate is restored by + // sigreturn(2). + // + // This is the reason why 'tgkill(getpid(), gettid(), SIGUSR1)' is implemented + // in inline assembly below. + // + // If the OS is broken and registers are clobbered by the signal, using tgkill + // to signal the current thread ensures that this is the clobbered thread. + SET_XMM(kOldFPRegValue, xmm0); + + asm volatile( + "movl %[killnr], %%eax;" + "movl %[pid], %%edi;" + "movl %[tid], %%esi;" + "movl %[sig], %%edx;" + "syscall;" + : + : [killnr] "i"(__NR_tgkill), [pid] "rm"(pid), [tid] "rm"(tid), + [sig] "i"(SIGUSR1) + : "rax", "rdi", "rsi", "rdx", + // Clobbered by syscall. + "rcx", "r11"); + + uint64_t got; + GET_XMM(got, xmm0); + + // + // The checks below verifies the following: + // - signal handlers must called with a clean fpu state. + // - sigreturn(2) must restore fpstate of the interrupted context. + // + EXPECT_EQ(handlerxmm, 0); + EXPECT_EQ(framexmm, kOldFPRegValue); + EXPECT_EQ(got, kNewFPRegValue); +} + +} // namespace + +} // namespace testing +} // namespace gvisor