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(®s, 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, ®s, 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", ®s) 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", ®s) 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 }