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  }