505 lines
15 KiB
Go
505 lines
15 KiB
Go
// Copyright 2021 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package mitigate
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// cpuTestCase represents data from CPUs that will be mitigated.
|
|
type cpuTestCase struct {
|
|
name string
|
|
vendorID string
|
|
family int
|
|
model int
|
|
modelName string
|
|
bugs string
|
|
physicalCores int
|
|
cores int
|
|
threadsPerCore int
|
|
}
|
|
|
|
var cascadeLake4 = cpuTestCase{
|
|
name: "CascadeLake",
|
|
vendorID: "GenuineIntel",
|
|
family: 6,
|
|
model: 85,
|
|
modelName: "Intel(R) Xeon(R) CPU",
|
|
bugs: "spectre_v1 spectre_v2 spec_store_bypass mds swapgs taa",
|
|
physicalCores: 1,
|
|
cores: 2,
|
|
threadsPerCore: 2,
|
|
}
|
|
|
|
var haswell2 = cpuTestCase{
|
|
name: "Haswell",
|
|
vendorID: "GenuineIntel",
|
|
family: 6,
|
|
model: 63,
|
|
modelName: "Intel(R) Xeon(R) CPU",
|
|
bugs: "cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs",
|
|
physicalCores: 1,
|
|
cores: 1,
|
|
threadsPerCore: 2,
|
|
}
|
|
|
|
var haswell2core = cpuTestCase{
|
|
name: "Haswell2Physical",
|
|
vendorID: "GenuineIntel",
|
|
family: 6,
|
|
model: 63,
|
|
modelName: "Intel(R) Xeon(R) CPU",
|
|
bugs: "cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs",
|
|
physicalCores: 2,
|
|
cores: 1,
|
|
threadsPerCore: 1,
|
|
}
|
|
|
|
var amd8 = cpuTestCase{
|
|
name: "AMD",
|
|
vendorID: "AuthenticAMD",
|
|
family: 23,
|
|
model: 49,
|
|
modelName: "AMD EPYC 7B12",
|
|
bugs: "sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass",
|
|
physicalCores: 4,
|
|
cores: 1,
|
|
threadsPerCore: 2,
|
|
}
|
|
|
|
// makeCPUString makes a string formated like /proc/cpuinfo for each cpuTestCase
|
|
func (tc cpuTestCase) makeCPUString() string {
|
|
template := `processor : %d
|
|
vendor_id : %s
|
|
cpu family : %d
|
|
model : %d
|
|
model name : %s
|
|
physical id : %d
|
|
core id : %d
|
|
cpu cores : %d
|
|
bugs : %s
|
|
`
|
|
ret := ``
|
|
for i := 0; i < tc.physicalCores; i++ {
|
|
for j := 0; j < tc.cores; j++ {
|
|
for k := 0; k < tc.threadsPerCore; k++ {
|
|
processorNum := (i*tc.cores+j)*tc.threadsPerCore + k
|
|
ret += fmt.Sprintf(template,
|
|
processorNum, /*processor*/
|
|
tc.vendorID, /*vendor_id*/
|
|
tc.family, /*cpu family*/
|
|
tc.model, /*model*/
|
|
tc.modelName, /*model name*/
|
|
i, /*physical id*/
|
|
j, /*core id*/
|
|
tc.cores*tc.physicalCores, /*cpu cores*/
|
|
tc.bugs /*bugs*/)
|
|
}
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// TestMockCPUSet tests mock cpu test cases against the cpuSet functions.
|
|
func TestMockCPUSet(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
testCase cpuTestCase
|
|
isVulnerable bool
|
|
}{
|
|
{
|
|
testCase: amd8,
|
|
isVulnerable: false,
|
|
},
|
|
{
|
|
testCase: haswell2,
|
|
isVulnerable: true,
|
|
},
|
|
{
|
|
testCase: haswell2core,
|
|
isVulnerable: true,
|
|
},
|
|
|
|
{
|
|
testCase: cascadeLake4,
|
|
isVulnerable: true,
|
|
},
|
|
} {
|
|
t.Run(tc.testCase.name, func(t *testing.T) {
|
|
data := tc.testCase.makeCPUString()
|
|
vulnerable := func(t *thread) bool {
|
|
return t.isVulnerable()
|
|
}
|
|
set, err := newCPUSet([]byte(data), vulnerable)
|
|
if err != nil {
|
|
t.Fatalf("Failed to ")
|
|
}
|
|
remaining := set.getRemainingList()
|
|
// In the non-vulnerable case, no cores should be shutdown so all should remain.
|
|
want := tc.testCase.physicalCores * tc.testCase.cores * tc.testCase.threadsPerCore
|
|
if tc.isVulnerable {
|
|
want = tc.testCase.physicalCores * tc.testCase.cores
|
|
}
|
|
|
|
if want != len(remaining) {
|
|
t.Fatalf("Failed to shutdown the correct number of cores: want: %d got: %d", want, len(remaining))
|
|
}
|
|
|
|
if !tc.isVulnerable {
|
|
return
|
|
}
|
|
|
|
// If the set is vulnerable, we expect only 1 thread per hyperthread pair.
|
|
for _, r := range remaining {
|
|
if _, ok := set[r.id]; !ok {
|
|
t.Fatalf("Entry %+v not in map, there must be two entries in the same thread group.", r)
|
|
}
|
|
delete(set, r.id)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGetCPU tests basic parsing of single CPU strings from reading
|
|
// /proc/cpuinfo.
|
|
func TestGetCPU(t *testing.T) {
|
|
data := `processor : 0
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 85
|
|
physical id: 0
|
|
core id : 0
|
|
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa itlb_multihit
|
|
`
|
|
want := thread{
|
|
processorNumber: 0,
|
|
vendorID: "GenuineIntel",
|
|
cpuFamily: 6,
|
|
model: 85,
|
|
id: cpuID{
|
|
physicalID: 0,
|
|
coreID: 0,
|
|
},
|
|
bugs: map[string]struct{}{
|
|
"cpu_meltdown": struct{}{},
|
|
"spectre_v1": struct{}{},
|
|
"spectre_v2": struct{}{},
|
|
"spec_store_bypass": struct{}{},
|
|
"l1tf": struct{}{},
|
|
"mds": struct{}{},
|
|
"swapgs": struct{}{},
|
|
"taa": struct{}{},
|
|
"itlb_multihit": struct{}{},
|
|
},
|
|
}
|
|
|
|
got, err := newThread(data)
|
|
if err != nil {
|
|
t.Fatalf("getCpu failed with error: %v", err)
|
|
}
|
|
|
|
if !want.similarTo(got) {
|
|
t.Fatalf("Failed cpus not similar: got: %+v, want: %+v", got, want)
|
|
}
|
|
|
|
if !got.isVulnerable() {
|
|
t.Fatalf("Failed: cpu should be vulnerable.")
|
|
}
|
|
}
|
|
|
|
func TestInvalid(t *testing.T) {
|
|
result, err := getThreads(`something not a processor`)
|
|
if err == nil {
|
|
t.Fatalf("getCPU set didn't return an error: %+v", result)
|
|
}
|
|
|
|
if !strings.Contains(err.Error(), "no cpus") {
|
|
t.Fatalf("Incorrect error returned: %v", err)
|
|
}
|
|
}
|
|
|
|
// TestCPUSet tests getting the right number of CPUs from
|
|
// parsing full output of /proc/cpuinfo.
|
|
func TestCPUSet(t *testing.T) {
|
|
data := `processor : 0
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 63
|
|
model name : Intel(R) Xeon(R) CPU @ 2.30GHz
|
|
stepping : 0
|
|
microcode : 0x1
|
|
cpu MHz : 2299.998
|
|
cache size : 46080 KB
|
|
physical id : 0
|
|
siblings : 2
|
|
core id : 0
|
|
cpu cores : 1
|
|
apicid : 0
|
|
initial apicid : 0
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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
|
|
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs
|
|
bogomips : 4599.99
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 46 bits physical, 48 bits virtual
|
|
power management:
|
|
|
|
processor : 1
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 63
|
|
model name : Intel(R) Xeon(R) CPU @ 2.30GHz
|
|
stepping : 0
|
|
microcode : 0x1
|
|
cpu MHz : 2299.998
|
|
cache size : 46080 KB
|
|
physical id : 0
|
|
siblings : 2
|
|
core id : 0
|
|
cpu cores : 1
|
|
apicid : 1
|
|
initial apicid : 1
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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
|
|
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs
|
|
bogomips : 4599.99
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 46 bits physical, 48 bits virtual
|
|
power management:
|
|
`
|
|
cpuSet, err := getThreads(data)
|
|
if err != nil {
|
|
t.Fatalf("getCPUSet failed: %v", err)
|
|
}
|
|
|
|
wantCPULen := 2
|
|
if len(cpuSet) != wantCPULen {
|
|
t.Fatalf("Num CPU mismatch: want: %d, got: %d", wantCPULen, len(cpuSet))
|
|
}
|
|
|
|
wantCPU := thread{
|
|
vendorID: "GenuineIntel",
|
|
cpuFamily: 6,
|
|
model: 63,
|
|
bugs: map[string]struct{}{
|
|
"cpu_meltdown": struct{}{},
|
|
"spectre_v1": struct{}{},
|
|
"spectre_v2": struct{}{},
|
|
"spec_store_bypass": struct{}{},
|
|
"l1tf": struct{}{},
|
|
"mds": struct{}{},
|
|
"swapgs": struct{}{},
|
|
},
|
|
}
|
|
|
|
for _, c := range cpuSet {
|
|
if !wantCPU.similarTo(c) {
|
|
t.Fatalf("Failed cpus not equal: got: %+v, want: %+v", c, wantCPU)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestReadFile is a smoke test for parsing methods.
|
|
func TestReadFile(t *testing.T) {
|
|
data, err := ioutil.ReadFile("/proc/cpuinfo")
|
|
if err != nil {
|
|
t.Fatalf("Failed to read cpuinfo: %v", err)
|
|
}
|
|
|
|
vulnerable := func(t *thread) bool {
|
|
return t.isVulnerable()
|
|
}
|
|
|
|
set, err := newCPUSet(data, vulnerable)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse CPU data %v\n%s", err, data)
|
|
}
|
|
|
|
if len(set) < 1 {
|
|
t.Fatalf("Failed to parse any CPUs: %d", len(set))
|
|
}
|
|
|
|
t.Log(set)
|
|
}
|
|
|
|
// TestVulnerable tests if the isVulnerable method is correct
|
|
// among known CPUs in GCP.
|
|
func TestVulnerable(t *testing.T) {
|
|
const haswell = `processor : 0
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 63
|
|
model name : Intel(R) Xeon(R) CPU @ 2.30GHz
|
|
stepping : 0
|
|
microcode : 0x1
|
|
cpu MHz : 2299.998
|
|
cache size : 46080 KB
|
|
physical id : 0
|
|
siblings : 4
|
|
core id : 0
|
|
cpu cores : 2
|
|
apicid : 0
|
|
initial apicid : 0
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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
|
|
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs
|
|
bogomips : 4599.99
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 46 bits physical, 48 bits virtual
|
|
power management:`
|
|
|
|
const skylake = `processor : 0
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 85
|
|
model name : Intel(R) Xeon(R) CPU @ 2.00GHz
|
|
stepping : 3
|
|
microcode : 0x1
|
|
cpu MHz : 2000.180
|
|
cache size : 39424 KB
|
|
physical id : 0
|
|
siblings : 2
|
|
core id : 0
|
|
cpu cores : 1
|
|
apicid : 0
|
|
initial apicid : 0
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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
|
|
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs taa
|
|
bogomips : 4000.36
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 46 bits physical, 48 bits virtual
|
|
power management:`
|
|
|
|
const cascade = `processor : 0
|
|
vendor_id : GenuineIntel
|
|
cpu family : 6
|
|
model : 85
|
|
model name : Intel(R) Xeon(R) CPU
|
|
stepping : 7
|
|
microcode : 0x1
|
|
cpu MHz : 2800.198
|
|
cache size : 33792 KB
|
|
physical id : 0
|
|
siblings : 2
|
|
core id : 0
|
|
cpu cores : 1
|
|
apicid : 0
|
|
initial apicid : 0
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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 pclmu
|
|
lqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowpr
|
|
efetch invpcid_single ssbd ibrs ibpb stibp ibrs_enhanced fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid r
|
|
tm mpx avx512f avx512dq rdseed adx smap clflushopt clwb avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves a
|
|
rat avx512_vnni md_clear arch_capabilities
|
|
bugs : spectre_v1 spectre_v2 spec_store_bypass mds swapgs taa
|
|
bogomips : 5600.39
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 46 bits physical, 48 bits virtual
|
|
power management:`
|
|
|
|
const amd = `processor : 0
|
|
vendor_id : AuthenticAMD
|
|
cpu family : 23
|
|
model : 49
|
|
model name : AMD EPYC 7B12
|
|
stepping : 0
|
|
microcode : 0x1000065
|
|
cpu MHz : 2250.000
|
|
cache size : 512 KB
|
|
physical id : 0
|
|
siblings : 2
|
|
core id : 0
|
|
cpu cores : 1
|
|
apicid : 0
|
|
initial apicid : 0
|
|
fpu : yes
|
|
fpu_exception : yes
|
|
cpuid level : 13
|
|
wp : yes
|
|
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
|
|
bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass
|
|
bogomips : 4500.00
|
|
TLB size : 3072 4K pages
|
|
clflush size : 64
|
|
cache_alignment : 64
|
|
address sizes : 48 bits physical, 48 bits virtual
|
|
power management:`
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
cpuString string
|
|
vulnerable bool
|
|
}{
|
|
{
|
|
name: "haswell",
|
|
cpuString: haswell,
|
|
vulnerable: true,
|
|
}, {
|
|
name: "skylake",
|
|
cpuString: skylake,
|
|
vulnerable: true,
|
|
}, {
|
|
name: "amd",
|
|
cpuString: amd,
|
|
vulnerable: false,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
set, err := getThreads(tc.cpuString)
|
|
if err != nil {
|
|
t.Fatalf("Failed to getCPUSet:%v\n %s", err, tc.cpuString)
|
|
}
|
|
|
|
if len(set) < 1 {
|
|
t.Fatalf("Returned empty cpu set: %v", set)
|
|
}
|
|
|
|
for _, c := range set {
|
|
got := func() bool {
|
|
return c.isVulnerable()
|
|
}()
|
|
|
|
if got != tc.vulnerable {
|
|
t.Fatalf("Mismatch vulnerable for cpu %+s: got %t want: %t", tc.name, tc.vulnerable, got)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|