github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/platform/kvm/kvm_test.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package kvm
    16  
    17  import (
    18  	"math/rand"
    19  	"reflect"
    20  	"sync/atomic"
    21  	"testing"
    22  	"time"
    23  
    24  	"golang.org/x/sys/unix"
    25  	"github.com/SagerNet/gvisor/pkg/abi/linux"
    26  	"github.com/SagerNet/gvisor/pkg/hostarch"
    27  	"github.com/SagerNet/gvisor/pkg/ring0"
    28  	"github.com/SagerNet/gvisor/pkg/ring0/pagetables"
    29  	"github.com/SagerNet/gvisor/pkg/sentry/arch"
    30  	"github.com/SagerNet/gvisor/pkg/sentry/arch/fpu"
    31  	"github.com/SagerNet/gvisor/pkg/sentry/platform"
    32  	"github.com/SagerNet/gvisor/pkg/sentry/platform/kvm/testutil"
    33  	ktime "github.com/SagerNet/gvisor/pkg/sentry/time"
    34  )
    35  
    36  var dummyFPState = fpu.NewState()
    37  
    38  type testHarness interface {
    39  	Errorf(format string, args ...interface{})
    40  	Fatalf(format string, args ...interface{})
    41  }
    42  
    43  func kvmTest(t testHarness, setup func(*KVM), fn func(*vCPU) bool) {
    44  	// Create the machine.
    45  	deviceFile, err := OpenDevice()
    46  	if err != nil {
    47  		t.Fatalf("error opening device file: %v", err)
    48  	}
    49  	k, err := New(deviceFile)
    50  	if err != nil {
    51  		t.Fatalf("error creating KVM instance: %v", err)
    52  	}
    53  	defer k.machine.Destroy()
    54  
    55  	// Call additional setup.
    56  	if setup != nil {
    57  		setup(k)
    58  	}
    59  
    60  	var c *vCPU // For recovery.
    61  	defer func() {
    62  		redpill()
    63  		if c != nil {
    64  			k.machine.Put(c)
    65  		}
    66  	}()
    67  	for {
    68  		c = k.machine.Get()
    69  		if !fn(c) {
    70  			break
    71  		}
    72  
    73  		// We put the vCPU here and clear the value so that the
    74  		// deferred recovery will not re-put it above.
    75  		k.machine.Put(c)
    76  		c = nil
    77  	}
    78  }
    79  
    80  func bluepillTest(t testHarness, fn func(*vCPU)) {
    81  	kvmTest(t, nil, func(c *vCPU) bool {
    82  		bluepill(c)
    83  		fn(c)
    84  		return false
    85  	})
    86  }
    87  
    88  func TestKernelSyscall(t *testing.T) {
    89  	bluepillTest(t, func(c *vCPU) {
    90  		redpill() // Leave guest mode.
    91  		if got := atomic.LoadUint32(&c.state); got != vCPUUser {
    92  			t.Errorf("vCPU not in ready state: got %v", got)
    93  		}
    94  	})
    95  }
    96  
    97  func hostFault() {
    98  	defer func() {
    99  		recover()
   100  	}()
   101  	var foo *int
   102  	*foo = 0
   103  }
   104  
   105  func TestKernelFault(t *testing.T) {
   106  	hostFault() // Ensure recovery works.
   107  	bluepillTest(t, func(c *vCPU) {
   108  		hostFault()
   109  		if got := atomic.LoadUint32(&c.state); got != vCPUUser {
   110  			t.Errorf("vCPU not in ready state: got %v", got)
   111  		}
   112  	})
   113  }
   114  
   115  func TestKernelFloatingPoint(t *testing.T) {
   116  	bluepillTest(t, func(c *vCPU) {
   117  		if !testutil.FloatingPointWorks() {
   118  			t.Errorf("floating point does not work, and it should!")
   119  		}
   120  	})
   121  }
   122  
   123  func applicationTest(t testHarness, useHostMappings bool, target func(), fn func(*vCPU, *arch.Registers, *pagetables.PageTables) bool) {
   124  	// Initialize registers & page tables.
   125  	var (
   126  		regs arch.Registers
   127  		pt   *pagetables.PageTables
   128  	)
   129  	testutil.SetTestTarget(&regs, target)
   130  
   131  	kvmTest(t, func(k *KVM) {
   132  		// Create new page tables.
   133  		as, _, err := k.NewAddressSpace(nil /* invalidator */)
   134  		if err != nil {
   135  			t.Fatalf("can't create new address space: %v", err)
   136  		}
   137  		pt = as.(*addressSpace).pageTables
   138  
   139  		if useHostMappings {
   140  			// Apply the physical mappings to these page tables.
   141  			// (This is normally dangerous, since they point to
   142  			// physical pages that may not exist. This shouldn't be
   143  			// done for regular user code, but is fine for test
   144  			// purposes.)
   145  			applyPhysicalRegions(func(pr physicalRegion) bool {
   146  				pt.Map(hostarch.Addr(pr.virtual), pr.length, pagetables.MapOpts{
   147  					AccessType: hostarch.AnyAccess,
   148  					User:       true,
   149  				}, pr.physical)
   150  				return true // Keep iterating.
   151  			})
   152  		}
   153  	}, func(c *vCPU) bool {
   154  		// Invoke the function with the extra data.
   155  		return fn(c, &regs, pt)
   156  	})
   157  }
   158  
   159  func TestApplicationSyscall(t *testing.T) {
   160  	applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   161  		var si linux.SignalInfo
   162  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   163  			Registers:          regs,
   164  			FloatingPointState: &dummyFPState,
   165  			PageTables:         pt,
   166  			FullRestore:        true,
   167  		}, &si); err == platform.ErrContextInterrupt {
   168  			return true // Retry.
   169  		} else if err != nil {
   170  			t.Errorf("application syscall with full restore failed: %v", err)
   171  		}
   172  		return false
   173  	})
   174  	applicationTest(t, true, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   175  		var si linux.SignalInfo
   176  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   177  			Registers:          regs,
   178  			FloatingPointState: &dummyFPState,
   179  			PageTables:         pt,
   180  		}, &si); err == platform.ErrContextInterrupt {
   181  			return true // Retry.
   182  		} else if err != nil {
   183  			t.Errorf("application syscall with partial restore failed: %v", err)
   184  		}
   185  		return false
   186  	})
   187  }
   188  
   189  func TestApplicationFault(t *testing.T) {
   190  	applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   191  		testutil.SetTouchTarget(regs, nil) // Cause fault.
   192  		var si linux.SignalInfo
   193  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   194  			Registers:          regs,
   195  			FloatingPointState: &dummyFPState,
   196  			PageTables:         pt,
   197  			FullRestore:        true,
   198  		}, &si); err == platform.ErrContextInterrupt {
   199  			return true // Retry.
   200  		} else if err != platform.ErrContextSignal || si.Signo != int32(unix.SIGSEGV) {
   201  			t.Errorf("application fault with full restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal)
   202  		}
   203  		return false
   204  	})
   205  	applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   206  		testutil.SetTouchTarget(regs, nil) // Cause fault.
   207  		var si linux.SignalInfo
   208  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   209  			Registers:          regs,
   210  			FloatingPointState: &dummyFPState,
   211  			PageTables:         pt,
   212  		}, &si); err == platform.ErrContextInterrupt {
   213  			return true // Retry.
   214  		} else if err != platform.ErrContextSignal || si.Signo != int32(unix.SIGSEGV) {
   215  			t.Errorf("application fault with partial restore got (%v, %v), expected (%v, SIGSEGV)", err, si, platform.ErrContextSignal)
   216  		}
   217  		return false
   218  	})
   219  }
   220  
   221  func TestRegistersSyscall(t *testing.T) {
   222  	applicationTest(t, true, testutil.TwiddleRegsSyscall, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   223  		testutil.SetTestRegs(regs) // Fill values for all registers.
   224  		for {
   225  			var si linux.SignalInfo
   226  			if _, err := c.SwitchToUser(ring0.SwitchOpts{
   227  				Registers:          regs,
   228  				FloatingPointState: &dummyFPState,
   229  				PageTables:         pt,
   230  			}, &si); err == platform.ErrContextInterrupt {
   231  				continue // Retry.
   232  			} else if err != nil {
   233  				t.Errorf("application register check with partial restore got unexpected error: %v", err)
   234  			}
   235  			if err := testutil.CheckTestRegs(regs, false); err != nil {
   236  				t.Errorf("application register check with partial restore failed: %v", err)
   237  			}
   238  			break // Done.
   239  		}
   240  		return false
   241  	})
   242  }
   243  
   244  func TestRegistersFault(t *testing.T) {
   245  	applicationTest(t, true, testutil.TwiddleRegsFault, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   246  		testutil.SetTestRegs(regs) // Fill values for all registers.
   247  		for {
   248  			var si linux.SignalInfo
   249  			if _, err := c.SwitchToUser(ring0.SwitchOpts{
   250  				Registers:          regs,
   251  				FloatingPointState: &dummyFPState,
   252  				PageTables:         pt,
   253  				FullRestore:        true,
   254  			}, &si); err == platform.ErrContextInterrupt {
   255  				continue // Retry.
   256  			} else if err != platform.ErrContextSignal || si.Signo != int32(unix.SIGSEGV) {
   257  				t.Errorf("application register check with full restore got unexpected error: %v", err)
   258  			}
   259  			if err := testutil.CheckTestRegs(regs, true); err != nil {
   260  				t.Errorf("application register check with full restore failed: %v", err)
   261  			}
   262  			break // Done.
   263  		}
   264  		return false
   265  	})
   266  }
   267  
   268  func TestBounce(t *testing.T) {
   269  	applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   270  		go func() {
   271  			time.Sleep(time.Millisecond)
   272  			c.BounceToKernel()
   273  		}()
   274  		var si linux.SignalInfo
   275  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   276  			Registers:          regs,
   277  			FloatingPointState: &dummyFPState,
   278  			PageTables:         pt,
   279  		}, &si); err != platform.ErrContextInterrupt {
   280  			t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
   281  		}
   282  		return false
   283  	})
   284  	applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   285  		go func() {
   286  			time.Sleep(time.Millisecond)
   287  			c.BounceToKernel()
   288  		}()
   289  		var si linux.SignalInfo
   290  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   291  			Registers:          regs,
   292  			FloatingPointState: &dummyFPState,
   293  			PageTables:         pt,
   294  			FullRestore:        true,
   295  		}, &si); err != platform.ErrContextInterrupt {
   296  			t.Errorf("application full restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
   297  		}
   298  		return false
   299  	})
   300  }
   301  
   302  func TestBounceStress(t *testing.T) {
   303  	applicationTest(t, true, testutil.SpinLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   304  		randomSleep := func() {
   305  			// O(hundreds of microseconds) is appropriate to ensure
   306  			// different overlaps and different schedules.
   307  			if n := rand.Intn(1000); n > 100 {
   308  				time.Sleep(time.Duration(n) * time.Microsecond)
   309  			}
   310  		}
   311  		for i := 0; i < 1000; i++ {
   312  			// Start an asynchronously executing goroutine that
   313  			// calls Bounce at pseudo-random point in time.
   314  			// This should wind up calling Bounce when the
   315  			// kernel is in various stages of the switch.
   316  			go func() {
   317  				randomSleep()
   318  				c.BounceToKernel()
   319  			}()
   320  			randomSleep()
   321  			var si linux.SignalInfo
   322  			if _, err := c.SwitchToUser(ring0.SwitchOpts{
   323  				Registers:          regs,
   324  				FloatingPointState: &dummyFPState,
   325  				PageTables:         pt,
   326  			}, &si); err != platform.ErrContextInterrupt {
   327  				t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextInterrupt)
   328  			}
   329  			c.unlock()
   330  			randomSleep()
   331  			c.lock()
   332  		}
   333  		return false
   334  	})
   335  }
   336  
   337  func TestInvalidate(t *testing.T) {
   338  	var data uintptr // Used below.
   339  	applicationTest(t, true, testutil.Touch, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   340  		testutil.SetTouchTarget(regs, &data) // Read legitimate value.
   341  		for {
   342  			var si linux.SignalInfo
   343  			if _, err := c.SwitchToUser(ring0.SwitchOpts{
   344  				Registers:          regs,
   345  				FloatingPointState: &dummyFPState,
   346  				PageTables:         pt,
   347  			}, &si); err == platform.ErrContextInterrupt {
   348  				continue // Retry.
   349  			} else if err != nil {
   350  				t.Errorf("application partial restore: got %v, wanted nil", err)
   351  			}
   352  			break // Done.
   353  		}
   354  		// Unmap the page containing data & invalidate.
   355  		pt.Unmap(hostarch.Addr(reflect.ValueOf(&data).Pointer() & ^uintptr(hostarch.PageSize-1)), hostarch.PageSize)
   356  		for {
   357  			var si linux.SignalInfo
   358  			if _, err := c.SwitchToUser(ring0.SwitchOpts{
   359  				Registers:          regs,
   360  				FloatingPointState: &dummyFPState,
   361  				PageTables:         pt,
   362  				Flush:              true,
   363  			}, &si); err == platform.ErrContextInterrupt {
   364  				continue // Retry.
   365  			} else if err != platform.ErrContextSignal {
   366  				t.Errorf("application partial restore: got %v, wanted %v", err, platform.ErrContextSignal)
   367  			}
   368  			break // Success.
   369  		}
   370  		return false
   371  	})
   372  }
   373  
   374  // IsFault returns true iff the given signal represents a fault.
   375  func IsFault(err error, si *linux.SignalInfo) bool {
   376  	return err == platform.ErrContextSignal && si.Signo == int32(unix.SIGSEGV)
   377  }
   378  
   379  func TestEmptyAddressSpace(t *testing.T) {
   380  	applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   381  		var si linux.SignalInfo
   382  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   383  			Registers:          regs,
   384  			FloatingPointState: &dummyFPState,
   385  			PageTables:         pt,
   386  		}, &si); err == platform.ErrContextInterrupt {
   387  			return true // Retry.
   388  		} else if !IsFault(err, &si) {
   389  			t.Errorf("first fault with partial restore failed got %v", err)
   390  			t.Logf("registers: %#v", &regs)
   391  		}
   392  		return false
   393  	})
   394  	applicationTest(t, false, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   395  		var si linux.SignalInfo
   396  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   397  			Registers:          regs,
   398  			FloatingPointState: &dummyFPState,
   399  			PageTables:         pt,
   400  			FullRestore:        true,
   401  		}, &si); err == platform.ErrContextInterrupt {
   402  			return true // Retry.
   403  		} else if !IsFault(err, &si) {
   404  			t.Errorf("first fault with full restore failed got %v", err)
   405  			t.Logf("registers: %#v", &regs)
   406  		}
   407  		return false
   408  	})
   409  }
   410  
   411  func TestWrongVCPU(t *testing.T) {
   412  	kvmTest(t, nil, func(c1 *vCPU) bool {
   413  		kvmTest(t, nil, func(c2 *vCPU) bool {
   414  			// Basic test, one then the other.
   415  			bluepill(c1)
   416  			bluepill(c2)
   417  			if c1.guestExits == 0 {
   418  				// Check: vCPU1 will exit due to redpill() in bluepill(c2).
   419  				// Don't allow the test to proceed if this fails.
   420  				t.Fatalf("wrong vCPU#1 exits: vCPU1=%+v,vCPU2=%+v", c1, c2)
   421  			}
   422  
   423  			// Alternate vCPUs; we expect to need to trigger the
   424  			// wrong vCPU path on each switch.
   425  			for i := 0; i < 100; i++ {
   426  				bluepill(c1)
   427  				bluepill(c2)
   428  			}
   429  			if count := c1.guestExits; count < 90 {
   430  				t.Errorf("wrong vCPU#1 exits: vCPU1=%+v,vCPU2=%+v", c1, c2)
   431  			}
   432  			if count := c2.guestExits; count < 90 {
   433  				t.Errorf("wrong vCPU#2 exits: vCPU1=%+v,vCPU2=%+v", c1, c2)
   434  			}
   435  			return false
   436  		})
   437  		return false
   438  	})
   439  	kvmTest(t, nil, func(c1 *vCPU) bool {
   440  		kvmTest(t, nil, func(c2 *vCPU) bool {
   441  			bluepill(c1)
   442  			bluepill(c2)
   443  			return false
   444  		})
   445  		return false
   446  	})
   447  }
   448  
   449  func TestRdtsc(t *testing.T) {
   450  	var i int // Iteration count.
   451  	kvmTest(t, nil, func(c *vCPU) bool {
   452  		start := ktime.Rdtsc()
   453  		bluepill(c)
   454  		guest := ktime.Rdtsc()
   455  		redpill()
   456  		end := ktime.Rdtsc()
   457  		if start > guest || guest > end {
   458  			t.Errorf("inconsistent time: start=%d, guest=%d, end=%d", start, guest, end)
   459  		}
   460  		i++
   461  		return i < 100
   462  	})
   463  }
   464  
   465  func BenchmarkApplicationSyscall(b *testing.B) {
   466  	var (
   467  		i int // Iteration includes machine.Get() / machine.Put().
   468  		a int // Count for ErrContextInterrupt.
   469  	)
   470  	applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   471  		var si linux.SignalInfo
   472  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   473  			Registers:          regs,
   474  			FloatingPointState: &dummyFPState,
   475  			PageTables:         pt,
   476  		}, &si); err == platform.ErrContextInterrupt {
   477  			a++
   478  			return true // Ignore.
   479  		} else if err != nil {
   480  			b.Fatalf("benchmark failed: %v", err)
   481  		}
   482  		i++
   483  		return i < b.N
   484  	})
   485  	if a != 0 {
   486  		b.Logf("ErrContextInterrupt occurred %d times (in %d iterations).", a, a+i)
   487  	}
   488  }
   489  
   490  func BenchmarkKernelSyscall(b *testing.B) {
   491  	// Note that the target passed here is irrelevant, we never execute SwitchToUser.
   492  	applicationTest(b, true, testutil.Getpid, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   493  		// iteration does not include machine.Get() / machine.Put().
   494  		for i := 0; i < b.N; i++ {
   495  			testutil.Getpid()
   496  		}
   497  		return false
   498  	})
   499  }
   500  
   501  func BenchmarkWorldSwitchToUserRoundtrip(b *testing.B) {
   502  	// see BenchmarkApplicationSyscall.
   503  	var (
   504  		i int
   505  		a int
   506  	)
   507  	applicationTest(b, true, testutil.SyscallLoop, func(c *vCPU, regs *arch.Registers, pt *pagetables.PageTables) bool {
   508  		var si linux.SignalInfo
   509  		if _, err := c.SwitchToUser(ring0.SwitchOpts{
   510  			Registers:          regs,
   511  			FloatingPointState: &dummyFPState,
   512  			PageTables:         pt,
   513  		}, &si); err == platform.ErrContextInterrupt {
   514  			a++
   515  			return true // Ignore.
   516  		} else if err != nil {
   517  			b.Fatalf("benchmark failed: %v", err)
   518  		}
   519  		// This will intentionally cause the world switch. By executing
   520  		// a host syscall here, we force the transition between guest
   521  		// and host mode.
   522  		testutil.Getpid()
   523  		i++
   524  		return i < b.N
   525  	})
   526  	if a != 0 {
   527  		b.Logf("ErrContextInterrupt occurred %d times (in %d iterations).", a, a+i)
   528  	}
   529  }