diff options
author | Andrei Vagin <avagin@google.com> | 2021-03-23 18:44:38 -0700 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-03-23 18:46:37 -0700 |
commit | 56a9a13976ad800a8a34b194d35f0169d0a0bb23 (patch) | |
tree | cb4b7c4352dc90a8c4c4f469c788fd2c5c6fd0dd /pkg/sentry/arch/arch_x86.go | |
parent | 960155cdaad49ccea07e45152f124beeb7e7fdcc (diff) |
Move the code that manages floating-point state to a separate package
This change is inspired by Adin's cl/355256448.
PiperOrigin-RevId: 364695931
Diffstat (limited to 'pkg/sentry/arch/arch_x86.go')
-rw-r--r-- | pkg/sentry/arch/arch_x86.go | 217 |
1 files changed, 8 insertions, 209 deletions
diff --git a/pkg/sentry/arch/arch_x86.go b/pkg/sentry/arch/arch_x86.go index 641ada92f..e8e52d3a8 100644 --- a/pkg/sentry/arch/arch_x86.go +++ b/pkg/sentry/arch/arch_x86.go @@ -24,10 +24,9 @@ import ( "gvisor.dev/gvisor/pkg/abi/linux" "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/log" + "gvisor.dev/gvisor/pkg/sentry/arch/fpu" rpb "gvisor.dev/gvisor/pkg/sentry/arch/registers_go_proto" - "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/syserror" - "gvisor.dev/gvisor/pkg/usermem" ) // Registers represents the CPU registers for this architecture. @@ -111,57 +110,6 @@ var ( X86TrapFlag uint64 = (1 << 8) ) -// x86FPState is x86 floating point state. -type x86FPState []byte - -// initX86FPState (defined in asm files) sets up initial state. -func initX86FPState(data *FloatingPointData, useXsave bool) - -func newX86FPStateSlice() []byte { - size, align := cpuid.HostFeatureSet().ExtendedStateSize() - capacity := size - // Always use at least 4096 bytes. - // - // For the KVM platform, this state is a fixed 4096 bytes, so make sure - // that the underlying array is at _least_ that size otherwise we will - // corrupt random memory. This is not a pleasant thing to debug. - if capacity < 4096 { - capacity = 4096 - } - return alignedBytes(capacity, align)[:size] -} - -// newX86FPState returns an initialized floating point state. -// -// The returned state is large enough to store all floating point state -// supported by host, even if the app won't use much of it due to a restricted -// FeatureSet. Since they may still be able to see state not advertised by -// CPUID we must ensure it does not contain any sentry state. -func newX86FPState() x86FPState { - f := x86FPState(newX86FPStateSlice()) - initX86FPState(f.FloatingPointData(), cpuid.HostFeatureSet().UseXsave()) - return f -} - -// fork creates and returns an identical copy of the x86 floating point state. -func (f x86FPState) fork() x86FPState { - n := x86FPState(newX86FPStateSlice()) - copy(n, f) - return n -} - -// FloatingPointData returns the raw data pointer. -func (f x86FPState) FloatingPointData() *FloatingPointData { - return (*FloatingPointData)(&f[0]) -} - -// NewFloatingPointData returns a new floating point data blob. -// -// This is primarily for use in tests. -func NewFloatingPointData() *FloatingPointData { - return (*FloatingPointData)(&(newX86FPState()[0])) -} - // Proto returns a protobuf representation of the system registers in State. func (s State) Proto() *rpb.Registers { regs := &rpb.AMD64Registers{ @@ -200,7 +148,7 @@ func (s State) Proto() *rpb.Registers { func (s *State) Fork() State { return State{ Regs: s.Regs, - x86FPState: s.x86FPState.fork(), + fpState: s.fpState.Fork(), FeatureSet: s.FeatureSet, } } @@ -393,149 +341,6 @@ func isValidSegmentBase(reg uint64) bool { return reg < uint64(maxAddr64) } -// 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. -const ptraceFPRegsSize = 512 - -// PtraceGetFPRegs implements Context.PtraceGetFPRegs. -func (s *State) PtraceGetFPRegs(dst io.Writer) (int, error) { - return dst.Write(s.x86FPState[:ptraceFPRegsSize]) -} - -// PtraceSetFPRegs implements Context.PtraceSetFPRegs. -func (s *State) PtraceSetFPRegs(src io.Reader) (int, error) { - var f [ptraceFPRegsSize]byte - n, err := io.ReadFull(src, f[:]) - if err != nil { - return 0, err - } - // Force reserved bits in MXCSR to 0. This is consistent with Linux. - sanitizeMXCSR(x86FPState(f[:])) - // N.B. this only copies the beginning of the FP state, which - // corresponds to the FXSAVE area. - copy(s.x86FPState, f[:]) - return n, nil -} - -const ( - // mxcsrOffset is the offset in bytes of the MXCSR field from the start of - // the FXSAVE area. (Intel SDM Vol. 1, Table 10-2 "Format of an FXSAVE - // Area") - mxcsrOffset = 24 - - // mxcsrMaskOffset is the offset in bytes of the MXCSR_MASK field from the - // start of the FXSAVE area. - mxcsrMaskOffset = 28 -) - -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 x86FPState) { - mxcsr := usermem.ByteOrder.Uint32(f[mxcsrOffset:]) - initMXCSRMask.Do(func() { - temp := x86FPState(alignedBytes(uint(ptraceFPRegsSize), 16)) - initX86FPState(temp.FloatingPointData(), false /* useXsave */) - mxcsrMask = usermem.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 - usermem.ByteOrder.PutUint32(f[mxcsrOffset:], mxcsr) -} - -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 - // XSAVE header (64 bytes). Equivalently, minXstateBytes is GDB's - // X86_XSTATE_SSE_SIZE. - minXstateBytes = 512 + 64 - - // userXstateXCR0Offset is the offset in bytes of the USER_XSTATE_XCR0_WORD - // field in Linux's struct user_xstateregs, which is the type manipulated - // by ptrace(PTRACE_GET/SETREGSET, NT_X86_XSTATE). Equivalently, - // userXstateXCR0Offset is GDB's I386_LINUX_XSAVE_XCR0_OFFSET. - userXstateXCR0Offset = 464 - - // xstateBVOffset is the offset in bytes of the XSTATE_BV field in an x86 - // XSAVE area. - xstateBVOffset = 512 - - // xsaveHeaderZeroedOffset and xsaveHeaderZeroedBytes indicate parts of the - // XSAVE header that we coerce to zero: "Bytes 15:8 of the XSAVE header is - // a state-component bitmap called XCOMP_BV. ... Bytes 63:16 of the XSAVE - // header are reserved." - Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header". - // Linux ignores XCOMP_BV, but it's able to recover from XRSTOR #GP - // exceptions resulting from invalid values; we aren't. Linux also never - // uses the compacted format when doing XSAVE and doesn't even define the - // compaction extensions to XSAVE as a CPU feature, so for simplicity we - // assume no one is using them. - xsaveHeaderZeroedOffset = 512 + 8 - xsaveHeaderZeroedBytes = 64 - 8 -) - -func (s *State) ptraceGetXstateRegs(dst io.Writer, maxlen int) (int, error) { - // N.B. s.x86FPState may contain more state than the application - // expects. We only copy the subset that would be in their XSAVE area. - ess, _ := s.FeatureSet.ExtendedStateSize() - f := make([]byte, ess) - copy(f, s.x86FPState) - // "The XSAVE feature set does not use bytes 511:416; bytes 463:416 are - // reserved." - Intel SDM Vol 1., Section 13.4.1 "Legacy Region of an XSAVE - // Area". Linux uses the first 8 bytes of this area to store the OS XSTATE - // mask. GDB relies on this: see - // gdb/x86-linux-nat.c:x86_linux_read_description(). - usermem.ByteOrder.PutUint64(f[userXstateXCR0Offset:], s.FeatureSet.ValidXCR0Mask()) - if len(f) > maxlen { - f = f[:maxlen] - } - return dst.Write(f) -} - -func (s *State) ptraceSetXstateRegs(src io.Reader, maxlen int) (int, error) { - // Allow users to pass an xstate register set smaller than ours (they can - // mask bits out of XSTATE_BV), as long as it's at least minXstateBytes. - // Also allow users to pass a register set larger than ours; anything after - // their ExtendedStateSize will be ignored. (I think Linux technically - // permits setting a register set smaller than minXstateBytes, but it has - // the same silent truncation behavior in kernel/ptrace.c:ptrace_regset().) - if maxlen < minXstateBytes { - return 0, unix.EFAULT - } - ess, _ := s.FeatureSet.ExtendedStateSize() - if maxlen > int(ess) { - maxlen = int(ess) - } - f := make([]byte, maxlen) - if _, err := io.ReadFull(src, f); err != nil { - return 0, err - } - // Force reserved bits in MXCSR to 0. This is consistent with Linux. - sanitizeMXCSR(x86FPState(f)) - // Users can't enable *more* XCR0 bits than what we, and the CPU, support. - xstateBV := usermem.ByteOrder.Uint64(f[xstateBVOffset:]) - xstateBV &= s.FeatureSet.ValidXCR0Mask() - usermem.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.x86FPState, f), nil -} - // Register sets defined in include/uapi/linux/elf.h. const ( _NT_PRSTATUS = 1 @@ -552,12 +357,9 @@ func (s *State) PtraceGetRegSet(regset uintptr, dst io.Writer, maxlen int) (int, } return s.PtraceGetRegs(dst) case _NT_PRFPREG: - if maxlen < ptraceFPRegsSize { - return 0, syserror.EFAULT - } - return s.PtraceGetFPRegs(dst) + return s.fpState.PtraceGetFPRegs(dst, maxlen) case _NT_X86_XSTATE: - return s.ptraceGetXstateRegs(dst, maxlen) + return s.fpState.PtraceGetXstateRegs(dst, maxlen, s.FeatureSet) default: return 0, syserror.EINVAL } @@ -572,12 +374,9 @@ func (s *State) PtraceSetRegSet(regset uintptr, src io.Reader, maxlen int) (int, } return s.PtraceSetRegs(src) case _NT_PRFPREG: - if maxlen < ptraceFPRegsSize { - return 0, syserror.EFAULT - } - return s.PtraceSetFPRegs(src) + return s.fpState.PtraceSetFPRegs(src, maxlen) case _NT_X86_XSTATE: - return s.ptraceSetXstateRegs(src, maxlen) + return s.fpState.PtraceSetXstateRegs(src, maxlen, s.FeatureSet) default: return 0, syserror.EINVAL } @@ -609,10 +408,10 @@ func New(arch Arch, fs *cpuid.FeatureSet) Context { case AMD64: return &context64{ State{ - x86FPState: newX86FPState(), + fpState: fpu.NewState(), FeatureSet: fs, }, - []x86FPState(nil), + []fpu.State(nil), } } panic(fmt.Sprintf("unknown architecture %v", arch)) |