github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/mitigate/mitigate_test.go (about) 1 // Copyright 2021 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 // +build amd64 16 17 package mitigate 18 19 import ( 20 "fmt" 21 "io/ioutil" 22 "strings" 23 "testing" 24 25 "github.com/SagerNet/gvisor/runsc/mitigate/mock" 26 ) 27 28 // TestMockCPUSet tests mock cpu test cases against the cpuSet functions. 29 func TestMockCPUSet(t *testing.T) { 30 for _, tc := range []struct { 31 testCase mock.CPU 32 isVulnerable bool 33 }{ 34 { 35 testCase: mock.AMD8, 36 isVulnerable: false, 37 }, 38 { 39 testCase: mock.Haswell2, 40 isVulnerable: true, 41 }, 42 { 43 testCase: mock.Haswell2core, 44 isVulnerable: true, 45 }, 46 { 47 testCase: mock.CascadeLake2, 48 isVulnerable: true, 49 }, 50 { 51 testCase: mock.CascadeLake4, 52 isVulnerable: true, 53 }, 54 } { 55 t.Run(tc.testCase.Name, func(t *testing.T) { 56 data := tc.testCase.MakeCPUString() 57 set, err := NewCPUSet([]byte(data)) 58 if err != nil { 59 t.Fatalf("Failed to create cpuSet: %v", err) 60 } 61 62 t.Logf("data: %s", data) 63 64 for _, tg := range set { 65 if err := checkSorted(tg.threads); err != nil { 66 t.Fatalf("Failed to sort cpuSet: %v", err) 67 } 68 } 69 70 remaining := set.GetRemainingList() 71 // In the non-vulnerable case, no cores should be shutdown so all should remain. 72 want := tc.testCase.PhysicalCores * tc.testCase.Cores * tc.testCase.ThreadsPerCore 73 if tc.isVulnerable { 74 want = tc.testCase.PhysicalCores * tc.testCase.Cores 75 } 76 77 if want != len(remaining) { 78 t.Fatalf("Failed to shutdown the correct number of cores: want: %d got: %d", want, len(remaining)) 79 } 80 81 if !tc.isVulnerable { 82 return 83 } 84 85 // If the set is vulnerable, we expect only 1 thread per hyperthread pair. 86 for _, r := range remaining { 87 if _, ok := set[r.id]; !ok { 88 t.Fatalf("Entry %+v not in map, there must be two entries in the same thread group.", r) 89 } 90 delete(set, r.id) 91 } 92 93 possible := tc.testCase.MakeSysPossibleString() 94 set, err = NewCPUSetFromPossible([]byte(possible)) 95 if err != nil { 96 t.Fatalf("Failed to make cpuSet: %v", err) 97 } 98 99 want = tc.testCase.PhysicalCores * tc.testCase.Cores * tc.testCase.ThreadsPerCore 100 got := len(set.GetRemainingList()) 101 if got != want { 102 t.Fatalf("Returned the wrong number of CPUs want: %d got: %d", want, got) 103 } 104 }) 105 } 106 } 107 108 // TestGetCPU tests basic parsing of single CPU strings from reading 109 // /proc/cpuinfo. 110 func TestGetCPU(t *testing.T) { 111 data := `processor : 0 112 vendor_id : GenuineIntel 113 cpu family : 6 114 model : 85 115 physical id: 0 116 core id : 0 117 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit 118 ` 119 want := Thread{ 120 processorNumber: 0, 121 vendorID: "GenuineIntel", 122 cpuFamily: 6, 123 model: 85, 124 id: threadID{ 125 physicalID: 0, 126 coreID: 0, 127 }, 128 bugs: map[string]struct{}{ 129 "cpu_meltdown": struct{}{}, 130 "spectre_v1": struct{}{}, 131 "spectre_v2": struct{}{}, 132 "spec_store_bypass": struct{}{}, 133 "l1tf": struct{}{}, 134 "mds": struct{}{}, 135 "swapgs": struct{}{}, 136 "taa": struct{}{}, 137 "itlb_multihit": struct{}{}, 138 }, 139 } 140 141 got, err := newThread(data) 142 if err != nil { 143 t.Fatalf("getCpu failed with error: %v", err) 144 } 145 146 if !want.SimilarTo(got) { 147 t.Fatalf("Failed cpus not similar: got: %+v, want: %+v", got, want) 148 } 149 150 if !got.IsVulnerable() { 151 t.Fatalf("Failed: cpu should be vulnerable.") 152 } 153 } 154 155 func TestInvalid(t *testing.T) { 156 result, err := getThreads(`something not a processor`) 157 if err == nil { 158 t.Fatalf("getCPU set didn't return an error: %+v", result) 159 } 160 161 if !strings.Contains(err.Error(), "no cpus") { 162 t.Fatalf("Incorrect error returned: %v", err) 163 } 164 } 165 166 // TestCPUSet tests getting the right number of CPUs from 167 // parsing full output of /proc/cpuinfo. 168 func TestCPUSet(t *testing.T) { 169 data := `processor : 0 170 vendor_id : GenuineIntel 171 cpu family : 6 172 model : 63 173 model name : Intel(R) Xeon(R) CPU @ 2.30GHz 174 stepping : 0 175 microcode : 0x1 176 cpu MHz : 2299.998 177 cache size : 46080 KB 178 physical id : 0 179 siblings : 2 180 core id : 0 181 cpu cores : 1 182 apicid : 0 183 initial apicid : 0 184 fpu : yes 185 fpu_exception : yes 186 cpuid level : 13 187 wp : yes 188 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities 189 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs 190 bogomips : 4599.99 191 clflush size : 64 192 cache_alignment : 64 193 address sizes : 46 bits physical, 48 bits virtual 194 power management: 195 196 processor : 1 197 vendor_id : GenuineIntel 198 cpu family : 6 199 model : 63 200 model name : Intel(R) Xeon(R) CPU @ 2.30GHz 201 stepping : 0 202 microcode : 0x1 203 cpu MHz : 2299.998 204 cache size : 46080 KB 205 physical id : 0 206 siblings : 2 207 core id : 0 208 cpu cores : 1 209 apicid : 1 210 initial apicid : 1 211 fpu : yes 212 fpu_exception : yes 213 cpuid level : 13 214 wp : yes 215 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities 216 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs 217 bogomips : 4599.99 218 clflush size : 64 219 cache_alignment : 64 220 address sizes : 46 bits physical, 48 bits virtual 221 power management: 222 ` 223 cpuSet, err := getThreads(data) 224 if err != nil { 225 t.Fatalf("getCPUSet failed: %v", err) 226 } 227 228 wantCPULen := 2 229 if len(cpuSet) != wantCPULen { 230 t.Fatalf("Num CPU mismatch: want: %d, got: %d", wantCPULen, len(cpuSet)) 231 } 232 233 wantCPU := Thread{ 234 vendorID: "GenuineIntel", 235 cpuFamily: 6, 236 model: 63, 237 bugs: map[string]struct{}{ 238 "cpu_meltdown": struct{}{}, 239 "spectre_v1": struct{}{}, 240 "spectre_v2": struct{}{}, 241 "spec_store_bypass": struct{}{}, 242 "l1tf": struct{}{}, 243 "mds": struct{}{}, 244 "swapgs": struct{}{}, 245 }, 246 } 247 248 for _, c := range cpuSet { 249 if !wantCPU.SimilarTo(c) { 250 t.Fatalf("Failed cpus not equal: got: %+v, want: %+v", c, wantCPU) 251 } 252 } 253 } 254 255 // TestReadFile is a smoke test for parsing methods. 256 func TestReadFile(t *testing.T) { 257 data, err := ioutil.ReadFile("/proc/cpuinfo") 258 if err != nil { 259 t.Fatalf("Failed to read cpuinfo: %v", err) 260 } 261 262 set, err := NewCPUSet(data) 263 if err != nil { 264 t.Fatalf("Failed to parse CPU data %v\n%s", err, data) 265 } 266 267 for _, tg := range set { 268 if err := checkSorted(tg.threads); err != nil { 269 t.Fatalf("Failed to sort cpuSet: %v", err) 270 } 271 } 272 273 if len(set) < 1 { 274 t.Fatalf("Failed to parse any CPUs: %d", len(set)) 275 } 276 277 t.Log(set) 278 } 279 280 // TestVulnerable tests if the isVulnerable method is correct 281 // among known CPUs in GCP. 282 func TestVulnerable(t *testing.T) { 283 const haswell = `processor : 0 284 vendor_id : GenuineIntel 285 cpu family : 6 286 model : 63 287 model name : Intel(R) Xeon(R) CPU @ 2.30GHz 288 stepping : 0 289 microcode : 0x1 290 cpu MHz : 2299.998 291 cache size : 46080 KB 292 physical id : 0 293 siblings : 4 294 core id : 0 295 cpu cores : 2 296 apicid : 0 297 initial apicid : 0 298 fpu : yes 299 fpu_exception : yes 300 cpuid level : 13 301 wp : yes 302 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt arat md_clear arch_capabilities 303 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs 304 bogomips : 4599.99 305 clflush size : 64 306 cache_alignment : 64 307 address sizes : 46 bits physical, 48 bits virtual 308 power management:` 309 310 const skylake = `processor : 0 311 vendor_id : GenuineIntel 312 cpu family : 6 313 model : 85 314 model name : Intel(R) Xeon(R) CPU @ 2.00GHz 315 stepping : 3 316 microcode : 0x1 317 cpu MHz : 2000.180 318 cache size : 39424 KB 319 physical id : 0 320 siblings : 2 321 core id : 0 322 cpu cores : 1 323 apicid : 0 324 initial apicid : 0 325 fpu : yes 326 fpu_exception : yes 327 cpuid level : 13 328 wp : yes 329 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch invpcid_single pti ssbd ibrs ibpb stibp fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves arat md_clear arch_capabilities 330 bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa 331 bogomips : 4000.36 332 clflush size : 64 333 cache_alignment : 64 334 address sizes : 46 bits physical, 48 bits virtual 335 power management:` 336 337 const cascade = `processor : 0 338 vendor_id : GenuineIntel 339 cpu family : 6 340 model : 85 341 model name : Intel(R) Xeon(R) CPU 342 stepping : 7 343 microcode : 0x1 344 cpu MHz : 2800.198 345 cache size : 33792 KB 346 physical id : 0 347 siblings : 2 348 core id : 0 349 cpu cores : 1 350 apicid : 0 351 initial apicid : 0 352 fpu : yes 353 fpu_exception : yes 354 cpuid level : 13 355 wp : yes 356 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 357 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid tsc_known_freq pni pclmu 358 lqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr 359 efetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid r 360 tm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves a 361 rat avx512_vnni md_clear arch_capabilities 362 bugs : spectre_v1 spectre_v2 spec_store_bypass mds swapgs taa 363 bogomips : 5600.39 364 clflush size : 64 365 cache_alignment : 64 366 address sizes : 46 bits physical, 48 bits virtual 367 power management:` 368 369 const amd = `processor : 0 370 vendor_id : AuthenticAMD 371 cpu family : 23 372 model : 49 373 model name : AMD EPYC 7B12 374 stepping : 0 375 microcode : 0x1000065 376 cpu MHz : 2250.000 377 cache size : 512 KB 378 physical id : 0 379 siblings : 2 380 core id : 0 381 cpu cores : 1 382 apicid : 0 383 initial apicid : 0 384 fpu : yes 385 fpu_exception : yes 386 cpuid level : 13 387 wp : yes 388 flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext ssbd ibrs ibpb stibp vmmcall fsgsbase tsc_adjust bmi1 avx2 smep bmi2 rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 clzero xsaveerptr arat npt nrip_save umip rdpid 389 bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass 390 bogomips : 4500.00 391 TLB size : 3072 4K pages 392 clflush size : 64 393 cache_alignment : 64 394 address sizes : 48 bits physical, 48 bits virtual 395 power management:` 396 397 for _, tc := range []struct { 398 name string 399 cpuString string 400 vulnerable bool 401 }{ 402 { 403 name: "haswell", 404 cpuString: haswell, 405 vulnerable: true, 406 }, { 407 name: "skylake", 408 cpuString: skylake, 409 vulnerable: true, 410 }, { 411 name: "amd", 412 cpuString: amd, 413 vulnerable: false, 414 }, 415 } { 416 t.Run(tc.name, func(t *testing.T) { 417 set, err := getThreads(tc.cpuString) 418 if err != nil { 419 t.Fatalf("Failed to getCPUSet:%v\n %s", err, tc.cpuString) 420 } 421 422 if len(set) < 1 { 423 t.Fatalf("Returned empty cpu set: %v", set) 424 } 425 426 for _, c := range set { 427 got := func() bool { 428 return c.IsVulnerable() 429 }() 430 431 if got != tc.vulnerable { 432 t.Fatalf("Mismatch vulnerable for cpu %+s: got %t want: %t", tc.name, tc.vulnerable, got) 433 } 434 } 435 }) 436 } 437 } 438 439 func TestReverse(t *testing.T) { 440 const noParse = "-1-" 441 for _, tc := range []struct { 442 name string 443 output string 444 wantErr error 445 wantCount int 446 }{ 447 { 448 name: "base", 449 output: "0-7", 450 wantErr: nil, 451 wantCount: 8, 452 }, 453 { 454 name: "huge", 455 output: "0-111", 456 wantErr: nil, 457 wantCount: 112, 458 }, 459 { 460 name: "not zero", 461 output: "50-53", 462 wantErr: nil, 463 wantCount: 4, 464 }, 465 { 466 name: "small", 467 output: "0", 468 wantErr: nil, 469 wantCount: 1, 470 }, 471 { 472 name: "invalid order", 473 output: "10-6", 474 wantErr: fmt.Errorf("invalid cpu bounds from possible: begin: %d end: %d", 10, 6), 475 }, 476 { 477 name: "no parse", 478 output: noParse, 479 wantErr: fmt.Errorf(`mismatch regex from possible: %q`, noParse), 480 }, 481 } { 482 t.Run(tc.name, func(t *testing.T) { 483 threads, err := GetThreadsFromPossible([]byte(tc.output)) 484 485 switch { 486 case tc.wantErr == nil: 487 if err != nil { 488 t.Fatalf("Wanted nil err, got: %v", err) 489 } 490 case err == nil: 491 t.Fatalf("Want error: %v got: %v", tc.wantErr, err) 492 default: 493 if tc.wantErr.Error() != err.Error() { 494 t.Fatalf("Want error: %v got error: %v", tc.wantErr, err) 495 } 496 } 497 498 if len(threads) != tc.wantCount { 499 t.Fatalf("Want count: %d got: %d", tc.wantCount, len(threads)) 500 } 501 }) 502 } 503 } 504 505 func TestReverseSmoke(t *testing.T) { 506 data, err := ioutil.ReadFile("/sys/devices/system/cpu/possible") 507 if err != nil { 508 t.Fatalf("Failed to read from possible: %v", err) 509 } 510 threads, err := GetThreadsFromPossible(data) 511 if err != nil { 512 t.Fatalf("Could not parse possible output: %v", err) 513 } 514 515 if len(threads) <= 0 { 516 t.Fatalf("Didn't get any CPU cores: %d", len(threads)) 517 } 518 } 519 520 func checkSorted(threads []Thread) error { 521 if len(threads) < 2 { 522 return nil 523 } 524 last := threads[0].processorNumber 525 for _, t := range threads[1:] { 526 if last >= t.processorNumber { 527 return fmt.Errorf("threads out of order: thread %d before %d", t.processorNumber, last) 528 } 529 last = t.processorNumber 530 } 531 return nil 532 }