From ea84ec9a1729ef36e5c78badd0518512caae5847 Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Fri, 2 Jun 2023 17:15:11 -0700 Subject: [PATCH] kvm: add the extended state information into fpu states Otherwise, rt_sigreturn rejects it. PiperOrigin-RevId: 537445737 --- pkg/sentry/arch/fpu/BUILD | 2 + pkg/sentry/arch/fpu/fpu_amd64.go | 61 ++++++++++++++++++++++- pkg/sentry/arch/fpu/fpu_amd64_unsafe.go | 43 ++++++++++++++++ pkg/sentry/arch/signal_amd64.go | 52 +++++-------------- pkg/sentry/platform/kvm/bluepill_amd64.go | 1 + pkg/sentry/platform/kvm/kvm_amd64.go | 2 + 6 files changed, 120 insertions(+), 41 deletions(-) create mode 100644 pkg/sentry/arch/fpu/fpu_amd64_unsafe.go diff --git a/pkg/sentry/arch/fpu/BUILD b/pkg/sentry/arch/fpu/BUILD index daac61b00..04f4fd97f 100644 --- a/pkg/sentry/arch/fpu/BUILD +++ b/pkg/sentry/arch/fpu/BUILD @@ -11,9 +11,11 @@ go_library( "fpu.go", "fpu_amd64.go", "fpu_amd64.s", + "fpu_amd64_unsafe.go", "fpu_arm64.go", "fpu_unsafe.go", ], + marshal = True, visibility = ["//:sandbox"], deps = [ "//pkg/cpuid", diff --git a/pkg/sentry/arch/fpu/fpu_amd64.go b/pkg/sentry/arch/fpu/fpu_amd64.go index b3fb39e1b..5b95b94b4 100644 --- a/pkg/sentry/arch/fpu/fpu_amd64.go +++ b/pkg/sentry/arch/fpu/fpu_amd64.go @@ -27,12 +27,46 @@ import ( "gvisor.dev/gvisor/pkg/sync" ) +// FPSoftwareFrame is equivalent to struct _fpx_sw_bytes, the data stored by +// Linux in bytes 464:511 of the fxsave/xsave frame. +// +// +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 ( + // FP_XSTATE_MAGIC1 is the value of FPSoftwareFrame.Magic1. + FP_XSTATE_MAGIC1 = 0x46505853 + // FP_SW_FRAME_OFFSET is the offset of FPSoftwareFrame in the + // fxsave/xsave area. + FP_SW_FRAME_OFFSET = 464 + + // FP_XSTATE_MAGIC2 is the 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 is the size of FP_XSTATE_MAGIC2. + FP_XSTATE_MAGIC2_SIZE = 4 +) + +// From Linux's arch/x86/include/asm/fpu/types.h. +const ( + // XFEATURE_MASK_FPSSE is xsave features that are always enabled in + // signal frame fpstate. + XFEATURE_MASK_FPSSE = 0x3 +) + // initX86FPState (defined in asm files) sets up initial state. func initX86FPState(data *byte, useXsave bool) func newX86FPStateSlice() State { size, align := cpuid.HostFeatureSet().ExtendedStateSize() - capacity := size + capacity := size + FP_XSTATE_MAGIC2_SIZE // Always use at least 4096 bytes. // // For the KVM platform, this state is a fixed 4096 bytes, so make sure @@ -41,7 +75,13 @@ func newX86FPStateSlice() State { if capacity < 4096 { capacity = 4096 } - return alignedBytes(capacity, align)[:size] + return alignedBytes(capacity, align)[:size+FP_XSTATE_MAGIC2_SIZE] +} + +// Slice returns the byte array that contains only the fpu state. `s` has the +// fpu state and FP_XSTATE_MAGIC2. +func (s State) Slice() []byte { + return s[:len(s)-FP_XSTATE_MAGIC2_SIZE] } // NewState returns an initialized floating point state. @@ -72,6 +112,23 @@ func (s *State) Reset() { initX86FPState(&f[0], cpuid.HostFeatureSet().UseXsave()) } +var ( + hostXCR0Mask uint64 + hostFPSize uint + hostUseXsave bool + initHostStateOnce sync.Once +) + +// InitHostState initializes host parameters. +func InitHostState() { + initHostStateOnce.Do(func() { + featureSet := cpuid.HostFeatureSet() + hostXCR0Mask = featureSet.ValidXCR0Mask() + hostUseXsave = featureSet.UseXsave() + hostFPSize, _ = featureSet.ExtendedStateSize() + }) +} + // 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. diff --git a/pkg/sentry/arch/fpu/fpu_amd64_unsafe.go b/pkg/sentry/arch/fpu/fpu_amd64_unsafe.go new file mode 100644 index 000000000..1a65658b2 --- /dev/null +++ b/pkg/sentry/arch/fpu/fpu_amd64_unsafe.go @@ -0,0 +1,43 @@ +// 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. + +//go:build amd64 || i386 +// +build amd64 i386 + +package fpu + +import ( + "unsafe" +) + +// PrepForHostSigframe prepare the SW reserved portion of the fxsave memory +// layout and adds FP_XSTATE_MAGIC2. It has to be called if the state is +// restored by rt_sigreturn. +// +// Look at save_xstate_epilog in the kernel sources for more details. +// +//go:nosplit +func (s State) PrepForHostSigframe() { + fpsw := (*FPSoftwareFrame)(unsafe.Pointer(&s[FP_SW_FRAME_OFFSET])) + fpsw.Magic1 = FP_XSTATE_MAGIC1 + fpsw.ExtendedSize = uint32(hostFPSize) + FP_XSTATE_MAGIC2_SIZE + fpsw.Xfeatures = XFEATURE_MASK_FPSSE | hostXCR0Mask + fpsw.XstateSize = uint32(hostFPSize) + + if !hostUseXsave { + return + } + + *(*uint32)(unsafe.Pointer(&s[hostFPSize])) = FP_XSTATE_MAGIC2 +} diff --git a/pkg/sentry/arch/signal_amd64.go b/pkg/sentry/arch/signal_amd64.go index b47faf6d9..e9eca2a70 100644 --- a/pkg/sentry/arch/signal_amd64.go +++ b/pkg/sentry/arch/signal_amd64.go @@ -26,6 +26,7 @@ import ( "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/hostarch" "gvisor.dev/gvisor/pkg/marshal/primitive" + "gvisor.dev/gvisor/pkg/sentry/arch/fpu" "gvisor.dev/gvisor/pkg/usermem" ) @@ -83,35 +84,6 @@ type UContext64 struct { Sigset linux.SignalSet } -// FPSoftwareFrame is equivalent to struct _fpx_sw_bytes, the data stored by -// Linux in bytes 464:511 of the fxsave/xsave frame. -// -// +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, featureSet cpuid.FeatureSet) error { @@ -134,7 +106,7 @@ func (c *Context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig // We expect support for at least FXSAVE. fpSize = 512 } - fpSize += _FP_XSTATE_MAGIC2_SIZE + fpSize += fpu.FP_XSTATE_MAGIC2_SIZE fpStart := (sp - hostarch.Addr(fpSize)) & ^hostarch.Addr(fpAlign-1) // Construct the UContext64 now since we need its size. @@ -195,28 +167,29 @@ func (c *Context64) SignalSetup(st *Stack, act *linux.SigAction, info *linux.Sig return unix.EFAULT } + fpState := c.fpState.Slice() // 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 { + if _, err := st.IO.CopyOut(context.Background(), fpStart, fpState[:fpu.FP_SW_FRAME_OFFSET], usermem.IOOpts{}); err != nil { return err } - fpsw := FPSoftwareFrame{ - Magic1: _FP_XSTATE_MAGIC1, + fpsw := fpu.FPSoftwareFrame{ + Magic1: fpu.FP_XSTATE_MAGIC1, ExtendedSize: uint32(fpSize), - Xfeatures: _XFEATURE_MASK_FPSSE | featureSet.ValidXCR0Mask(), - XstateSize: uint32(fpSize) - _FP_XSTATE_MAGIC2_SIZE, + Xfeatures: fpu.XFEATURE_MASK_FPSSE | featureSet.ValidXCR0Mask(), + XstateSize: uint32(fpSize) - fpu.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 { + if len(fpState) > 512 { + if _, err := st.IO.CopyOut(context.Background(), fpStart+512, 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 { + if _, err := primitive.CopyUint32Out(st, StackBottomMagic, fpu.FP_XSTATE_MAGIC2); err != nil { return err } @@ -304,7 +277,8 @@ func (c *Context64) SignalRestore(st *Stack, rt bool, featureSet cpuid.FeatureSe if uc.MContext.Fpstate == 0 { c.fpState.Reset() } else { - if _, err := st.IO.CopyIn(context.Background(), hostarch.Addr(uc.MContext.Fpstate), c.fpState, usermem.IOOpts{}); err != nil { + fpState := c.fpState.Slice() + if _, err := st.IO.CopyIn(context.Background(), hostarch.Addr(uc.MContext.Fpstate), fpState, usermem.IOOpts{}); err != nil { c.fpState.Reset() return 0, linux.SignalStack{}, err } diff --git a/pkg/sentry/platform/kvm/bluepill_amd64.go b/pkg/sentry/platform/kvm/bluepill_amd64.go index c1be709a8..9e2fbc87f 100644 --- a/pkg/sentry/platform/kvm/bluepill_amd64.go +++ b/pkg/sentry/platform/kvm/bluepill_amd64.go @@ -162,6 +162,7 @@ func bluepillArchExit(c *vCPU, context *arch.SignalContext64) { context.Rip = regs.Rip context.Eflags = regs.Eflags + c.FloatingPointState().PrepForHostSigframe() // Set the context pointer to the saved floating point state. This is // where the guest data has been serialized, the kernel will restore // from this new pointer value. diff --git a/pkg/sentry/platform/kvm/kvm_amd64.go b/pkg/sentry/platform/kvm/kvm_amd64.go index c61cdf9e0..2e682e8a6 100644 --- a/pkg/sentry/platform/kvm/kvm_amd64.go +++ b/pkg/sentry/platform/kvm/kvm_amd64.go @@ -20,6 +20,7 @@ package kvm import ( "gvisor.dev/gvisor/pkg/cpuid" "gvisor.dev/gvisor/pkg/ring0" + "gvisor.dev/gvisor/pkg/sentry/arch/fpu" ) // userRegs represents KVM user registers. @@ -219,6 +220,7 @@ func (c *cpuidEntries) Set(in cpuid.In, out cpuid.Out) { // updateGlobalOnce does global initialization. It has to be called only once. func updateGlobalOnce(fd int) error { + fpu.InitHostState() bitsForScaling = getBitsForScaling() if err := updateSystemValues(int(fd)); err != nil { return err