diff options
-rw-r--r-- | pkg/sentry/platform/kvm/context.go | 7 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/kvm_test.go | 88 | ||||
-rw-r--r-- | pkg/sentry/platform/kvm/machine_amd64.go | 65 |
3 files changed, 89 insertions, 71 deletions
diff --git a/pkg/sentry/platform/kvm/context.go b/pkg/sentry/platform/kvm/context.go index be902be88..c75a4b415 100644 --- a/pkg/sentry/platform/kvm/context.go +++ b/pkg/sentry/platform/kvm/context.go @@ -29,6 +29,9 @@ type context struct { // machine is the parent machine, and is immutable. machine *machine + // info is the arch.SignalInfo cached for this context. + info arch.SignalInfo + // interrupt is the interrupt context. interrupt interrupt.Forwarder } @@ -65,7 +68,7 @@ func (c *context) Switch(as platform.AddressSpace, ac arch.Context, _ int32) (*a } // Take the blue pill. - si, at, err := cpu.SwitchToUser(switchOpts) + at, err := cpu.SwitchToUser(switchOpts, &c.info) // Clear the address space. cpu.active.set(nil) @@ -75,7 +78,7 @@ func (c *context) Switch(as platform.AddressSpace, ac arch.Context, _ int32) (*a // All done. c.interrupt.Disable() - return si, at, err + return &c.info, at, err } // Interrupt interrupts the running context. diff --git a/pkg/sentry/platform/kvm/kvm_test.go b/pkg/sentry/platform/kvm/kvm_test.go index 45eeb96ff..fff463a6e 100644 --- a/pkg/sentry/platform/kvm/kvm_test.go +++ b/pkg/sentry/platform/kvm/kvm_test.go @@ -156,12 +156,13 @@ func applicationTest(t testHarness, useHostMappings bool, target func(), fn func func TestApplicationSyscall(t *testing.T) { applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. } else if err != nil { t.Errorf("application syscall with full restore failed: %v", err) @@ -169,11 +170,12 @@ func TestApplicationSyscall(t *testing.T) { return false }) applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. } else if err != nil { t.Errorf("application syscall with partial restore failed: %v", err) @@ -185,27 +187,29 @@ func TestApplicationSyscall(t *testing.T) { func TestApplicationFault(t *testing.T) { applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTouchTarget(regs, nil) // Cause fault. - if si, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. - } else if err != platform.ErrContextSignal || (si != nil && si.Signo != int32(syscall.SIGSEGV)) { + } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) { t.Errorf("application fault with full restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal) } return false }) applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTouchTarget(regs, nil) // Cause fault. - if si, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. - } else if err != platform.ErrContextSignal || (si != nil && si.Signo != int32(syscall.SIGSEGV)) { + } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) { t.Errorf("application fault with partial restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal) } return false @@ -216,11 +220,12 @@ func TestRegistersSyscall(t *testing.T) { applicationTest(t, true, testutil.TwiddleRegsSyscall, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTestRegs(regs) // Fill values for all registers. for { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { continue // Retry. } else if err != nil { t.Errorf("application register check with partial restore got unexpected error: %v", err) @@ -238,12 +243,13 @@ func TestRegistersFault(t *testing.T) { applicationTest(t, true, testutil.TwiddleRegsFault, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTestRegs(regs) // Fill values for all registers. for { - if si, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { continue // Retry. } else if err != platform.ErrContextSignal || si.Signo != int32(syscall.SIGSEGV) { t.Errorf("application register check with full restore got unexpected error: %v", err) @@ -261,12 +267,13 @@ func TestSegments(t *testing.T) { applicationTest(t, true, testutil.TwiddleSegments, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTestSegments(regs) for { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { continue // Retry. } else if err != nil { t.Errorf("application segment check with full restore got unexpected error: %v", err) @@ -286,11 +293,12 @@ func TestBounce(t *testing.T) { time.Sleep(time.Millisecond) c.BounceToKernel() }() - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err != platform.ErrContextInterrupt { + }, &si); err != platform.ErrContextInterrupt { t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt) } return false @@ -300,12 +308,13 @@ func TestBounce(t *testing.T) { time.Sleep(time.Millisecond) c.BounceToKernel() }() - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err != platform.ErrContextInterrupt { + }, &si); err != platform.ErrContextInterrupt { t.Errorf("application full restore: got %v, wanted %v", err, platform.ErrContextInterrupt) } return false @@ -331,11 +340,12 @@ func TestBounceStress(t *testing.T) { c.BounceToKernel() }() randomSleep() - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err != platform.ErrContextInterrupt { + }, &si); err != platform.ErrContextInterrupt { t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt) } c.unlock() @@ -351,11 +361,12 @@ func TestInvalidate(t *testing.T) { applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { testutil.SetTouchTarget(regs, &data) // Read legitimate value. for { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { continue // Retry. } else if err != nil { t.Errorf("application partial restore: got %v, wanted nil", err) @@ -365,12 +376,13 @@ func TestInvalidate(t *testing.T) { // Unmap the page containing data & invalidate. pt.Unmap(usermem.Addr(reflect.ValueOf(&data).Pointer() & ^uintptr(usermem.PageSize-1)), usermem.PageSize) for { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, Flush: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { continue // Retry. } else if err != platform.ErrContextSignal { t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextSignal) @@ -388,27 +400,29 @@ func IsFault(err error, si *arch.SignalInfo) bool { func TestEmptyAddressSpace(t *testing.T) { applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if si, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. - } else if !IsFault(err, si) { + } else if !IsFault(err, &si) { t.Errorf("first fault with partial restore failed got %v", err) t.Logf("registers: %#v", ®s) } return false }) applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if si, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, FullRestore: true, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { return true // Retry. - } else if !IsFault(err, si) { + } else if !IsFault(err, &si) { t.Errorf("first fault with full restore failed got %v", err) t.Logf("registers: %#v", ®s) } @@ -459,11 +473,12 @@ func BenchmarkApplicationSyscall(b *testing.B) { a int // Count for ErrContextInterrupt. ) applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { a++ return true // Ignore. } else if err != nil { @@ -495,11 +510,12 @@ func BenchmarkWorldSwitchToUserRoundtrip(b *testing.B) { a int ) applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *syscall.PtraceRegs, pt *pagetables.PageTables) bool { - if _, _, err := c.SwitchToUser(ring0.SwitchOpts{ + var si arch.SignalInfo + if _, err := c.SwitchToUser(ring0.SwitchOpts{ Registers: regs, FloatingPointState: dummyFPState, PageTables: pt, - }); err == platform.ErrContextInterrupt { + }, &si); err == platform.ErrContextInterrupt { a++ return true // Ignore. } else if err != nil { diff --git a/pkg/sentry/platform/kvm/machine_amd64.go b/pkg/sentry/platform/kvm/machine_amd64.go index e0aec42b8..c03792a1b 100644 --- a/pkg/sentry/platform/kvm/machine_amd64.go +++ b/pkg/sentry/platform/kvm/machine_amd64.go @@ -156,19 +156,19 @@ func (c *vCPU) initArchState() error { // nonCanonical generates a canonical address return. // //go:nosplit -func nonCanonical(addr uint64, signal int32) (*arch.SignalInfo, usermem.AccessType, error) { - info := &arch.SignalInfo{ +func nonCanonical(addr uint64, signal int32, info *arch.SignalInfo) (usermem.AccessType, error) { + *info = arch.SignalInfo{ Signo: signal, Code: arch.SignalInfoKernel, } info.SetAddr(addr) // Include address. - return info, usermem.NoAccess, platform.ErrContextSignal + return usermem.NoAccess, platform.ErrContextSignal } // fault generates an appropriate fault return. // //go:nosplit -func (c *vCPU) fault(signal int32) (*arch.SignalInfo, usermem.AccessType, error) { +func (c *vCPU) fault(signal int32, info *arch.SignalInfo) (usermem.AccessType, error) { bluepill(c) // Probably no-op, but may not be. faultAddr := ring0.ReadCR2() code, user := c.ErrorCode() @@ -176,11 +176,10 @@ func (c *vCPU) fault(signal int32) (*arch.SignalInfo, usermem.AccessType, error) // The last fault serviced by this CPU was not a user // fault, so we can't reliably trust the faultAddr or // the code provided here. We need to re-execute. - return nil, usermem.NoAccess, platform.ErrContextInterrupt - } - info := &arch.SignalInfo{ - Signo: signal, + return usermem.NoAccess, platform.ErrContextInterrupt } + // Reset the pointed SignalInfo. + *info = arch.SignalInfo{Signo: signal} info.SetAddr(uint64(faultAddr)) accessType := usermem.AccessType{ Read: code&(1<<1) == 0, @@ -192,20 +191,20 @@ func (c *vCPU) fault(signal int32) (*arch.SignalInfo, usermem.AccessType, error) } else { info.Code = 2 // SEGV_ACCERR. } - return info, accessType, platform.ErrContextSignal + return accessType, platform.ErrContextSignal } // SwitchToUser unpacks architectural-details. -func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts) (*arch.SignalInfo, usermem.AccessType, error) { +func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts, info *arch.SignalInfo) (usermem.AccessType, error) { // Check for canonical addresses. if regs := switchOpts.Registers; !ring0.IsCanonical(regs.Rip) { - return nonCanonical(regs.Rip, int32(syscall.SIGSEGV)) + return nonCanonical(regs.Rip, int32(syscall.SIGSEGV), info) } else if !ring0.IsCanonical(regs.Rsp) { - return nonCanonical(regs.Rsp, int32(syscall.SIGBUS)) + return nonCanonical(regs.Rsp, int32(syscall.SIGBUS), info) } else if !ring0.IsCanonical(regs.Fs_base) { - return nonCanonical(regs.Fs_base, int32(syscall.SIGBUS)) + return nonCanonical(regs.Fs_base, int32(syscall.SIGBUS), info) } else if !ring0.IsCanonical(regs.Gs_base) { - return nonCanonical(regs.Gs_base, int32(syscall.SIGBUS)) + return nonCanonical(regs.Gs_base, int32(syscall.SIGBUS), info) } // Assign PCIDs. @@ -231,25 +230,25 @@ func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts) (*arch.SignalInfo, user switch vector { case ring0.Syscall, ring0.SyscallInt80: // Fast path: system call executed. - return nil, usermem.NoAccess, nil + return usermem.NoAccess, nil case ring0.PageFault: - return c.fault(int32(syscall.SIGSEGV)) + return c.fault(int32(syscall.SIGSEGV), info) case ring0.Debug, ring0.Breakpoint: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGTRAP), Code: 1, // TRAP_BRKPT (breakpoint). } info.SetAddr(switchOpts.Registers.Rip) // Include address. - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.GeneralProtectionFault, ring0.SegmentNotPresent, ring0.BoundRangeExceeded, ring0.InvalidTSS, ring0.StackSegmentFault: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGSEGV), Code: arch.SignalInfoKernel, } @@ -258,52 +257,52 @@ func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts) (*arch.SignalInfo, user // When CPUID faulting is enabled, we will generate a #GP(0) when // userspace executes a CPUID instruction. This is handled above, // because we need to be able to map and read user memory. - return info, usermem.AccessType{}, platform.ErrContextSignalCPUID + return usermem.AccessType{}, platform.ErrContextSignalCPUID } - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.InvalidOpcode: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGILL), Code: 1, // ILL_ILLOPC (illegal opcode). } info.SetAddr(switchOpts.Registers.Rip) // Include address. - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.DivideByZero: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGFPE), Code: 1, // FPE_INTDIV (divide by zero). } info.SetAddr(switchOpts.Registers.Rip) // Include address. - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.Overflow: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGFPE), Code: 1, // FPE_INTOVF (integer overflow). } info.SetAddr(switchOpts.Registers.Rip) // Include address. - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.X87FloatingPointException, ring0.SIMDFloatingPointException: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGFPE), Code: 7, // FPE_FLTINV (invalid operation). } info.SetAddr(switchOpts.Registers.Rip) // Include address. - return info, usermem.AccessType{}, platform.ErrContextSignal + return usermem.AccessType{}, platform.ErrContextSignal case ring0.Vector(bounce): // ring0.VirtualizationException - return nil, usermem.NoAccess, platform.ErrContextInterrupt + return usermem.NoAccess, platform.ErrContextInterrupt case ring0.AlignmentCheck: - info := &arch.SignalInfo{ + *info = arch.SignalInfo{ Signo: int32(syscall.SIGBUS), Code: 2, // BUS_ADRERR (physical address does not exist). } - return info, usermem.NoAccess, platform.ErrContextSignal + return usermem.NoAccess, platform.ErrContextSignal case ring0.NMI: // An NMI is generated only when a fault is not servicable by @@ -311,7 +310,7 @@ func (c *vCPU) SwitchToUser(switchOpts ring0.SwitchOpts) (*arch.SignalInfo, user // really not. This could happen, e.g. if some file is // truncated (and would generate a SIGBUS) and we map it // directly into the instance. - return c.fault(int32(syscall.SIGBUS)) + return c.fault(int32(syscall.SIGBUS), info) case ring0.DeviceNotAvailable, ring0.DoubleFault, |