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