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