Major refactor of runsc mitigate.

PiperOrigin-RevId: 362360425
This commit is contained in:
Zach Koopmans
2021-03-11 13:10:08 -08:00
committed by gVisor bot
parent 1020ac83f4
commit a82bd04e2a
11 changed files with 1339 additions and 1280 deletions
+3
View File
@@ -77,6 +77,7 @@ go_test(
"delete_test.go",
"exec_test.go",
"gofer_test.go",
"mitigate_test.go",
],
data = [
"//runsc",
@@ -91,6 +92,8 @@ go_test(
"//pkg/urpc",
"//runsc/config",
"//runsc/container",
"//runsc/mitigate",
"//runsc/mitigate/mock",
"//runsc/specutils",
"@com_github_google_go_cmp//cmp:go_default_library",
"@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
+115 -7
View File
@@ -16,6 +16,8 @@ package cmd
import (
"context"
"fmt"
"io/ioutil"
"github.com/google/subcommands"
"gvisor.dev/gvisor/pkg/log"
@@ -23,9 +25,23 @@ import (
"gvisor.dev/gvisor/runsc/mitigate"
)
const (
// cpuInfo is the path used to parse CPU info.
cpuInfo = "/proc/cpuinfo"
// allPossibleCPUs is the path used to enable CPUs.
allPossibleCPUs = "/sys/devices/system/cpu/possible"
)
// Mitigate implements subcommands.Command for the "mitigate" command.
type Mitigate struct {
mitigate mitigate.Mitigate
// Run the command without changing the underlying system.
dryRun bool
// Reverse mitigate by turning on all CPU cores.
reverse bool
// Path to file to read to create CPUSet.
path string
// Callback to check if a given thread is vulnerable.
vulnerable func(other mitigate.Thread) bool
}
// Name implements subcommands.command.name.
@@ -38,14 +54,19 @@ func (*Mitigate) Synopsis() string {
return "mitigate mitigates the underlying system against side channel attacks"
}
// Usage implements subcommands.Command.Usage.
func (m *Mitigate) Usage() string {
return m.mitigate.Usage()
// Usage implments Usage for cmd.Mitigate.
func (m Mitigate) Usage() string {
return `mitigate [flags]
mitigate mitigates a system to the "MDS" vulnerability by implementing a manual shutdown of SMT. The command checks /proc/cpuinfo for cpus having the MDS vulnerability, and if found, shutdown all but one CPU per hyperthread pair via /sys/devices/system/cpu/cpu{N}/online. CPUs can be restored by writing "2" to each file in /sys/devices/system/cpu/cpu{N}/online or performing a system reboot.
The command can be reversed with --reverse, which reads the total CPUs from /sys/devices/system/cpu/possible and enables all with /sys/devices/system/cpu/cpu{N}/online.`
}
// SetFlags implements subcommands.Command.SetFlags.
// SetFlags sets flags for the command Mitigate.
func (m *Mitigate) SetFlags(f *flag.FlagSet) {
m.mitigate.SetFlags(f)
f.BoolVar(&m.dryRun, "dryrun", false, "run the command without changing system")
f.BoolVar(&m.reverse, "reverse", false, "reverse mitigate by enabling all CPUs")
}
// Execute implements subcommands.Command.Execute.
@@ -55,10 +76,97 @@ func (m *Mitigate) Execute(_ context.Context, f *flag.FlagSet, args ...interface
return subcommands.ExitUsageError
}
if err := m.mitigate.Execute(); err != nil {
m.path = cpuInfo
if m.reverse {
m.path = allPossibleCPUs
}
m.vulnerable = func(other mitigate.Thread) bool {
return other.IsVulnerable()
}
if _, err := m.doExecute(); err != nil {
log.Warningf("Execute failed: %v", err)
return subcommands.ExitFailure
}
return subcommands.ExitSuccess
}
// Execute executes the Mitigate command.
func (m *Mitigate) doExecute() (mitigate.CPUSet, error) {
if m.dryRun {
log.Infof("Running with DryRun. No cpu settings will be changed.")
}
if m.reverse {
data, err := ioutil.ReadFile(m.path)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", m.path, err)
}
set, err := m.doReverse(data)
if err != nil {
return nil, fmt.Errorf("reverse operation failed: %v", err)
}
return set, nil
}
data, err := ioutil.ReadFile(m.path)
if err != nil {
return nil, fmt.Errorf("failed to read %s: %v", m.path, err)
}
set, err := m.doMitigate(data)
if err != nil {
return nil, fmt.Errorf("mitigate operation failed: %v", err)
}
return set, nil
}
func (m *Mitigate) doMitigate(data []byte) (mitigate.CPUSet, error) {
set, err := mitigate.NewCPUSet(data, m.vulnerable)
if err != nil {
return nil, err
}
log.Infof("Mitigate found the following CPUs...")
log.Infof("%s", set)
disableList := set.GetShutdownList()
log.Infof("Disabling threads on thread pairs.")
for _, t := range disableList {
log.Infof("Disable thread: %s", t)
if m.dryRun {
continue
}
if err := t.Disable(); err != nil {
return nil, fmt.Errorf("error disabling thread: %s err: %v", t, err)
}
}
log.Infof("Shutdown successful.")
return set, nil
}
func (m *Mitigate) doReverse(data []byte) (mitigate.CPUSet, error) {
set, err := mitigate.NewCPUSetFromPossible(data)
if err != nil {
return nil, err
}
log.Infof("Reverse mitigate found the following CPUs...")
log.Infof("%s", set)
enableList := set.GetRemainingList()
log.Infof("Enabling all CPUs...")
for _, t := range enableList {
log.Infof("Enabling thread: %s", t)
if m.dryRun {
continue
}
if err := t.Enable(); err != nil {
return nil, fmt.Errorf("error enabling thread: %s err: %v", t, err)
}
}
log.Infof("Enable successful.")
return set, nil
}
+169
View File
@@ -0,0 +1,169 @@
// 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 cmd
import (
"fmt"
"io/ioutil"
"os"
"strings"
"testing"
"gvisor.dev/gvisor/runsc/mitigate"
"gvisor.dev/gvisor/runsc/mitigate/mock"
)
type executeTestCase struct {
name string
mitigateData string
mitigateError error
mitigateCPU int
reverseData string
reverseError error
reverseCPU int
}
func TestExecute(t *testing.T) {
partial := `processor : 1
vendor_id : AuthenticAMD
cpu family : 23
model : 49
model name : AMD EPYC 7B12
physical id : 0
bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass
power management:
`
for _, tc := range []executeTestCase{
{
name: "CascadeLake4",
mitigateData: mock.CascadeLake4.MakeCPUString(),
mitigateCPU: 2,
reverseData: mock.CascadeLake4.MakeSysPossibleString(),
reverseCPU: 4,
},
{
name: "Empty",
mitigateData: "",
mitigateError: fmt.Errorf(`mitigate operation failed: no cpus found for: ""`),
reverseData: "",
reverseError: fmt.Errorf(`reverse operation failed: mismatch regex from possible: ""`),
},
{
name: "Partial",
mitigateData: `processor : 0
vendor_id : AuthenticAMD
cpu family : 23
model : 49
model name : AMD EPYC 7B12
physical id : 0
core id : 0
cpu cores : 1
bugs : sysret_ss_attrs spectre_v1 spectre_v2 spec_store_bypass
power management::84
` + partial,
mitigateError: fmt.Errorf(`mitigate operation failed: failed to match key "core id": %q`, partial),
reverseData: "1-",
reverseError: fmt.Errorf(`reverse operation failed: mismatch regex from possible: %q`, "1-"),
},
} {
t.Run(tc.name, func(t *testing.T) {
m := &Mitigate{
dryRun: true,
vulnerable: func(other mitigate.Thread) bool {
return other.IsVulnerable()
},
}
m.doExecuteTest(t, "Mitigate", tc.mitigateData, tc.mitigateCPU, tc.mitigateError)
m.reverse = true
m.doExecuteTest(t, "Reverse", tc.reverseData, tc.reverseCPU, tc.reverseError)
})
}
}
func TestExecuteSmoke(t *testing.T) {
smokeMitigate, err := ioutil.ReadFile(cpuInfo)
if err != nil {
t.Fatalf("Failed to read %s: %v", cpuInfo, err)
}
m := &Mitigate{
dryRun: true,
vulnerable: func(other mitigate.Thread) bool {
return other.IsVulnerable()
},
}
m.doExecuteTest(t, "Mitigate", string(smokeMitigate), 0, nil)
smokeReverse, err := ioutil.ReadFile(allPossibleCPUs)
if err != nil {
t.Fatalf("Failed to read %s: %v", allPossibleCPUs, err)
}
m.reverse = true
m.doExecuteTest(t, "Reverse", string(smokeReverse), 0, nil)
}
// doExecuteTest runs Execute with the mitigate operation and reverse operation.
func (m *Mitigate) doExecuteTest(t *testing.T, name, data string, want int, wantErr error) {
t.Run(name, func(t *testing.T) {
file, err := ioutil.TempFile("", "outfile.txt")
if err != nil {
t.Fatalf("Failed to create tmpfile: %v", err)
}
defer os.Remove(file.Name())
if _, err := file.WriteString(data); err != nil {
t.Fatalf("Failed to write to file: %v", err)
}
// Set fields for mitigate and dryrun to keep test hermetic.
m.path = file.Name()
set, err := m.doExecute()
if err = checkErr(wantErr, err); err != nil {
t.Fatalf("Mitigate error mismatch: %v", err)
}
// case where test should end in error or we don't care
// about how many cpus are returned.
if wantErr != nil || want < 1 {
return
}
got := len(set.GetRemainingList())
if want != got {
t.Fatalf("Failed wrong number of remaining CPUs: want %d, got %d", want, got)
}
})
}
// checkErr checks error for equality.
func checkErr(want, got error) error {
switch {
case want == nil && got == nil:
case want != nil && got == nil:
fallthrough
case want == nil && got != nil:
fallthrough
case want.Error() != strings.Trim(got.Error(), " "):
return fmt.Errorf("got: %v want: %v", got, want)
}
return nil
}
+7 -15
View File
@@ -4,28 +4,20 @@ package(licenses = ["notice"])
go_library(
name = "mitigate",
srcs = [
"cpu.go",
"mitigate.go",
"mitigate_conf.go",
],
srcs = ["mitigate.go"],
visibility = [
"//runsc:__subpackages__",
],
deps = [
"//pkg/log",
"//runsc/flag",
"@in_gopkg_yaml_v2//:go_default_library",
],
deps = ["@in_gopkg_yaml_v2//:go_default_library"],
)
go_test(
name = "mitigate_test",
size = "small",
srcs = [
"cpu_test.go",
"mitigate_test.go",
],
srcs = ["mitigate_test.go"],
library = ":mitigate",
deps = ["@com_github_google_go_cmp//cmp:go_default_library"],
deps = [
"//runsc/mitigate/mock",
"@com_github_google_go_cmp//cmp:go_default_library",
],
)
-423
View File
@@ -1,423 +0,0 @@
// 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"
"regexp"
"strconv"
"strings"
)
const (
// mds is the only bug we care about.
mds = "mds"
// Constants for parsing /proc/cpuinfo.
processorKey = "processor"
vendorIDKey = "vendor_id"
cpuFamilyKey = "cpu family"
modelKey = "model"
physicalIDKey = "physical id"
coreIDKey = "core id"
bugsKey = "bugs"
// Path to shutdown a CPU.
cpuOnlineTemplate = "/sys/devices/system/cpu/cpu%d/online"
)
// cpuSet contains a map of all CPUs on the system, mapped
// by Physical ID and CoreIDs. threads with the same
// Core and Physical ID are Hyperthread pairs.
type cpuSet map[cpuID]*threadGroup
// newCPUSet creates a CPUSet from data read from /proc/cpuinfo.
func newCPUSet(data []byte, vulnerable func(thread) bool) (cpuSet, error) {
processors, err := getThreads(string(data))
if err != nil {
return nil, err
}
set := make(cpuSet)
for _, p := range processors {
// Each ID is of the form physicalID:coreID. Hyperthread pairs
// have identical physical and core IDs. We need to match
// Hyperthread pairs so that we can shutdown all but one per
// pair.
core, ok := set[p.id]
if !ok {
core = &threadGroup{}
set[p.id] = core
}
core.isVulnerable = core.isVulnerable || vulnerable(p)
core.threads = append(core.threads, p)
}
return set, nil
}
// newCPUSetFromPossible makes a cpuSet data read from
// /sys/devices/system/cpu/possible. This is used in enable operations
// where the caller simply wants to enable all CPUS.
func newCPUSetFromPossible(data []byte) (cpuSet, error) {
threads, err := getThreadsFromPossible(data)
if err != nil {
return nil, err
}
// We don't care if a CPU is vulnerable or not, we just
// want to return a list of all CPUs on the host.
set := cpuSet{
threads[0].id: &threadGroup{
threads: threads,
isVulnerable: false,
},
}
return set, nil
}
// String implements the String method for CPUSet.
func (c cpuSet) String() string {
ret := ""
for _, tg := range c {
ret += fmt.Sprintf("%s\n", tg)
}
return ret
}
// getRemainingList returns the list of threads that will remain active
// after mitigation.
func (c cpuSet) getRemainingList() []thread {
threads := make([]thread, 0, len(c))
for _, core := range c {
// If we're vulnerable, take only one thread from the pair.
if core.isVulnerable {
threads = append(threads, core.threads[0])
continue
}
// Otherwise don't shutdown anything.
threads = append(threads, core.threads...)
}
return threads
}
// getShutdownList returns the list of threads that will be shutdown on
// mitigation.
func (c cpuSet) getShutdownList() []thread {
threads := make([]thread, 0)
for _, core := range c {
// Only if we're vulnerable do shutdown anything. In this case,
// shutdown all but the first entry.
if core.isVulnerable && len(core.threads) > 1 {
threads = append(threads, core.threads[1:]...)
}
}
return threads
}
// threadGroup represents Hyperthread pairs on the same physical/core ID.
type threadGroup struct {
threads []thread
isVulnerable bool
}
// String implements the String method for threadGroup.
func (c threadGroup) String() string {
ret := fmt.Sprintf("ThreadGroup:\nIsVulnerable: %t\n", c.isVulnerable)
for _, processor := range c.threads {
ret += fmt.Sprintf("%s\n", processor)
}
return ret
}
// getThreads returns threads structs from reading /proc/cpuinfo.
func getThreads(data string) ([]thread, error) {
// Each processor entry should start with the
// processor key. Find the beginings of each.
r := buildRegex(processorKey, `\d+`)
indices := r.FindAllStringIndex(data, -1)
if len(indices) < 1 {
return nil, fmt.Errorf("no cpus found for: %q", data)
}
// Add the ending index for last entry.
indices = append(indices, []int{len(data), -1})
// Valid cpus are now defined by strings in between
// indexes (e.g. data[index[i], index[i+1]]).
// There should be len(indicies) - 1 CPUs
// since the last index is the end of the string.
cpus := make([]thread, 0, len(indices))
// Find each string that represents a CPU. These begin "processor".
for i := 1; i < len(indices); i++ {
start := indices[i-1][0]
end := indices[i][0]
// Parse the CPU entry, which should be between start/end.
c, err := newThread(data[start:end])
if err != nil {
return nil, err
}
cpus = append(cpus, c)
}
return cpus, nil
}
// getThreadsFromPossible makes threads from data read from /sys/devices/system/cpu/possible.
func getThreadsFromPossible(data []byte) ([]thread, error) {
possibleRegex := regexp.MustCompile(`(?m)^(\d+)(-(\d+))?$`)
matches := possibleRegex.FindStringSubmatch(string(data))
if len(matches) != 4 {
return nil, fmt.Errorf("mismatch regex from %s: %q", allPossibleCPUs, string(data))
}
// If matches[3] is empty, we only have one cpu entry.
if matches[3] == "" {
matches[3] = matches[1]
}
begin, err := strconv.ParseInt(matches[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse begin: %v", err)
}
end, err := strconv.ParseInt(matches[3], 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse end: %v", err)
}
if begin > end || begin < 0 || end < 0 {
return nil, fmt.Errorf("invalid cpu bounds from possible: begin: %d end: %d", begin, end)
}
ret := make([]thread, 0, end-begin)
for i := begin; i <= end; i++ {
ret = append(ret, thread{
processorNumber: i,
id: cpuID{
physicalID: 0, // we don't care about id for enable ops.
coreID: 0,
},
})
}
return ret, nil
}
// cpuID for each thread is defined by the physical and
// core IDs. If equal, two threads are Hyperthread pairs.
type cpuID struct {
physicalID int64
coreID int64
}
// type cpu represents pertinent info about a cpu.
type thread struct {
processorNumber int64 // the processor number of this CPU.
vendorID string // the vendorID of CPU (e.g. AuthenticAMD).
cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake).
model int64 // CPU model number (e.g. 85 for CascadeLake/Skylake).
id cpuID // id for this thread
bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field.
}
// newThread parses a CPU from a single cpu entry from /proc/cpuinfo.
func newThread(data string) (thread, error) {
empty := thread{}
processor, err := parseProcessor(data)
if err != nil {
return empty, err
}
vendorID, err := parseVendorID(data)
if err != nil {
return empty, err
}
cpuFamily, err := parseCPUFamily(data)
if err != nil {
return empty, err
}
model, err := parseModel(data)
if err != nil {
return empty, err
}
physicalID, err := parsePhysicalID(data)
if err != nil {
return empty, err
}
coreID, err := parseCoreID(data)
if err != nil {
return empty, err
}
bugs, err := parseBugs(data)
if err != nil {
return empty, err
}
return thread{
processorNumber: processor,
vendorID: vendorID,
cpuFamily: cpuFamily,
model: model,
id: cpuID{
physicalID: physicalID,
coreID: coreID,
},
bugs: bugs,
}, nil
}
// String implements the String method for thread.
func (t thread) String() string {
template := `CPU: %d
CPU ID: %+v
Vendor: %s
Family/Model: %d/%d
Bugs: %s
`
bugs := make([]string, 0)
for bug := range t.bugs {
bugs = append(bugs, bug)
}
return fmt.Sprintf(template, t.processorNumber, t.id, t.vendorID, t.cpuFamily, t.model, strings.Join(bugs, ","))
}
// enable turns on the CPU by writing 1 to /sys/devices/cpu/cpu{N}/online.
func (t thread) enable() error {
cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber)
return ioutil.WriteFile(cpuPath, []byte{'1'}, 0644)
}
// disable turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online.
func (t thread) disable() error {
cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber)
return ioutil.WriteFile(cpuPath, []byte{'0'}, 0644)
}
// isVulnerable checks if a CPU is vulnerable to mds.
func (t thread) isVulnerable() bool {
_, ok := t.bugs[mds]
return ok
}
// isActive checks if a CPU is active from /sys/devices/system/cpu/cpu{N}/online
// If the file does not exist (ioutil returns in error), we assume the CPU is on.
func (t thread) isActive() bool {
cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber)
data, err := ioutil.ReadFile(cpuPath)
if err != nil {
return true
}
return len(data) > 0 && data[0] != '0'
}
// similarTo checks family/model/bugs fields for equality of two
// processors.
func (t thread) similarTo(other thread) bool {
if t.vendorID != other.vendorID {
return false
}
if other.cpuFamily != t.cpuFamily {
return false
}
if other.model != t.model {
return false
}
if len(other.bugs) != len(t.bugs) {
return false
}
for bug := range t.bugs {
if _, ok := other.bugs[bug]; !ok {
return false
}
}
return true
}
// parseProcessor grabs the processor field from /proc/cpuinfo output.
func parseProcessor(data string) (int64, error) {
return parseIntegerResult(data, processorKey)
}
// parseVendorID grabs the vendor_id field from /proc/cpuinfo output.
func parseVendorID(data string) (string, error) {
return parseRegex(data, vendorIDKey, `[\w\d]+`)
}
// parseCPUFamily grabs the cpu family field from /proc/cpuinfo output.
func parseCPUFamily(data string) (int64, error) {
return parseIntegerResult(data, cpuFamilyKey)
}
// parseModel grabs the model field from /proc/cpuinfo output.
func parseModel(data string) (int64, error) {
return parseIntegerResult(data, modelKey)
}
// parsePhysicalID parses the physical id field.
func parsePhysicalID(data string) (int64, error) {
return parseIntegerResult(data, physicalIDKey)
}
// parseCoreID parses the core id field.
func parseCoreID(data string) (int64, error) {
return parseIntegerResult(data, coreIDKey)
}
// parseBugs grabs the bugs field from /proc/cpuinfo output.
func parseBugs(data string) (map[string]struct{}, error) {
result, err := parseRegex(data, bugsKey, `[\d\w\s]*`)
if err != nil {
return nil, err
}
bugs := strings.Split(result, " ")
ret := make(map[string]struct{}, len(bugs))
for _, bug := range bugs {
ret[bug] = struct{}{}
}
return ret, nil
}
// parseIntegerResult parses fields expecting an integer.
func parseIntegerResult(data, key string) (int64, error) {
result, err := parseRegex(data, key, `\d+`)
if err != nil {
return 0, err
}
return strconv.ParseInt(result, 0, 64)
}
// buildRegex builds a regex for parsing each CPU field.
func buildRegex(key, match string) *regexp.Regexp {
reg := fmt.Sprintf(`(?m)^%s\s*:\s*(.*)$`, key)
return regexp.MustCompile(reg)
}
// parseRegex parses data with key inserted into a standard regex template.
func parseRegex(data, key, match string) (string, error) {
r := buildRegex(key, match)
matches := r.FindStringSubmatch(data)
if len(matches) < 2 {
return "", fmt.Errorf("failed to match key %q: %q", key, data)
}
return matches[1], nil
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-37
View File
@@ -1,37 +0,0 @@
// 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 (
"gvisor.dev/gvisor/runsc/flag"
)
type mitigate struct {
}
// usage returns the usage string portion for the mitigate.
func (m mitigate) usage() string { return "" }
// setFlags sets additional flags for the Mitigate command.
func (m mitigate) setFlags(f *flag.FlagSet) {}
// execute performs additional parts of Execute for Mitigate.
func (m mitigate) execute(set cpuSet, dryrun bool) error {
return nil
}
func (m mitigate) vulnerable(other thread) bool {
return other.isVulnerable()
}
File diff suppressed because it is too large Load Diff
+11
View File
@@ -0,0 +1,11 @@
load("//tools:defs.bzl", "go_library")
package(licenses = ["notice"])
go_library(
name = "mock",
srcs = ["mock.go"],
visibility = [
"//runsc:__subpackages__",
],
)
+141
View File
@@ -0,0 +1,141 @@
// 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 mock contains mock CPUs for mitigate tests.
package mock
import "fmt"
// CPU represents data from CPUs that will be mitigated.
type CPU struct {
Name string
VendorID string
Family int
Model int
ModelName string
Bugs string
PhysicalCores int
Cores int
ThreadsPerCore int
}
// CascadeLake2 is a two core Intel CascadeLake machine.
var CascadeLake2 = CPU{
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: 1,
ThreadsPerCore: 2,
}
// CascadeLake4 is a four core Intel CascadeLake machine.
var CascadeLake4 = CPU{
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,
}
// Haswell2 is a two core Intel Haswell machine.
var Haswell2 = CPU{
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,
}
// Haswell2core is a 2 core Intel Haswell machine with no hyperthread pairs.
var Haswell2core = CPU{
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,
}
// AMD8 is an eight core AMD machine.
var AMD8 = CPU{
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 CPU) 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
}
// MakeSysPossibleString makes a string representing a the contents of /sys/devices/system/cpu/possible.
func (tc CPU) MakeSysPossibleString() string {
max := tc.PhysicalCores * tc.Cores * tc.ThreadsPerCore
if max == 1 {
return "0"
}
return fmt.Sprintf("0-%d", max-1)
}