diff options
author | gVisor bot <gvisor-bot@google.com> | 2021-03-12 01:06:00 +0000 |
---|---|---|
committer | gVisor bot <gvisor-bot@google.com> | 2021-03-12 01:06:00 +0000 |
commit | 8a7964657411d80c685128531099d6c246216f12 (patch) | |
tree | 43a12cad4774774951da1548519e59ecff0050ae /runsc | |
parent | 3bbc07ece91af55682430e6ae2a30060f5f7fd9c (diff) | |
parent | c5667022b6617d732e0c0bcb8ca3b58d588ceafb (diff) |
Merge release-20210301.0-37-gc5667022b (automated)
Diffstat (limited to 'runsc')
-rw-r--r-- | runsc/cmd/mitigate.go | 122 | ||||
-rw-r--r-- | runsc/mitigate/cpu.go | 423 | ||||
-rw-r--r-- | runsc/mitigate/mitigate.go | 467 | ||||
-rw-r--r-- | runsc/mitigate/mitigate_conf.go | 37 |
4 files changed, 508 insertions, 541 deletions
diff --git a/runsc/cmd/mitigate.go b/runsc/cmd/mitigate.go index 822af1917..fddf0e0dd 100644 --- a/runsc/cmd/mitigate.go +++ b/runsc/cmd/mitigate.go @@ -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 +} diff --git a/runsc/mitigate/cpu.go b/runsc/mitigate/cpu.go deleted file mode 100644 index 4b2aa351f..000000000 --- a/runsc/mitigate/cpu.go +++ /dev/null @@ -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 -} diff --git a/runsc/mitigate/mitigate.go b/runsc/mitigate/mitigate.go index 91de623e3..24f67414c 100644 --- a/runsc/mitigate/mitigate.go +++ b/runsc/mitigate/mitigate.go @@ -14,121 +14,440 @@ // Package mitigate provides libraries for the mitigate command. The // mitigate command mitigates side channel attacks such as MDS. Mitigate -// shuts down CPUs via /sys/devices/system/cpu/cpu{N}/online. In addition, -// the mitigate also handles computing available CPU in kubernetes kube_config -// files. +// shuts down CPUs via /sys/devices/system/cpu/cpu{N}/online. package mitigate import ( "fmt" "io/ioutil" - - "gvisor.dev/gvisor/pkg/log" - "gvisor.dev/gvisor/runsc/flag" + "os" + "regexp" + "sort" + "strconv" + "strings" ) const ( - cpuInfo = "/proc/cpuinfo" - allPossibleCPUs = "/sys/devices/system/cpu/possible" + // 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" ) -// Mitigate handles high level mitigate operations provided to runsc. -type Mitigate struct { - dryRun bool // Run the command without changing the underlying system. - reverse bool // Reverse mitigate by turning on all CPU cores. - other mitigate // Struct holds extra mitigate logic. - path string // path to read for each operation (e.g. /proc/cpuinfo). +// 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[threadID]*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) + } + + // We need to make sure we shutdown the lowest number processor per + // thread group. + for _, tg := range set { + sort.Slice(tg.threads, func(i, j int) bool { + return tg.threads[i].processorNumber < tg.threads[j].processorNumber + }) + } + return set, nil } -// Usage implments Usage for cmd.Mitigate. -func (m Mitigate) Usage() string { - usageString := `mitigate [flags] +// 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 +} -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. +// 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 +} -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. -` - return usageString + m.other.usage() +// 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 } -// SetFlags sets flags for the command Mitigate. -func (m Mitigate) SetFlags(f *flag.FlagSet) { - f.BoolVar(&m.dryRun, "dryrun", false, "run the command without changing system") - f.BoolVar(&m.reverse, "reverse", false, "reverse mitigate by enabling all CPUs") - m.other.setFlags(f) - m.path = cpuInfo - if m.reverse { - m.path = allPossibleCPUs +// 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 } -// Execute executes the Mitigate command. -func (m Mitigate) Execute() error { - data, err := ioutil.ReadFile(m.path) - if err != nil { - return fmt.Errorf("failed to read %s: %v", m.path, err) +// 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 +} - if m.reverse { - err := m.doReverse(data) +// 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 fmt.Errorf("reverse operation failed: %v", err) + return nil, err } - return nil + 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 possible: %q", string(data)) + } + + // If matches[3] is empty, we only have one cpu entry. + if matches[3] == "" { + matches[3] = matches[1] } - set, err := m.doMitigate(data) + begin, err := strconv.ParseInt(matches[1], 10, 64) if err != nil { - return fmt.Errorf("mitigate operation failed: %v", err) + return nil, fmt.Errorf("failed to parse begin: %v", err) } - return m.other.execute(set, m.dryRun) + 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: threadID{ + physicalID: 0, // we don't care about id for enable ops. + coreID: 0, + }, + }) + } + + return ret, nil +} + +// threadID for each thread is defined by the physical and +// core IDs. If equal, two threads are Hyperthread pairs. +type threadID struct { + physicalID int64 + coreID int64 } -func (m Mitigate) doMitigate(data []byte) (cpuSet, error) { - set, err := newCPUSet(data, m.other.vulnerable) +// Thread represents pertinent info about a single hyperthread in a pair. +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 threadID // 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 nil, err + return empty, err } - log.Infof("Mitigate found the following CPUs...") - log.Infof("%s", set) + vendorID, err := parseVendorID(data) + if err != nil { + return empty, err + } - 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) - } + cpuFamily, err := parseCPUFamily(data) + if err != nil { + return empty, err } - log.Infof("Shutdown successful.") - return set, nil + + 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: threadID{ + 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 { + // Linux ensures that "cpu0" is always online. + if t.processorNumber == 0 { + return nil + } + cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) + f, err := os.OpenFile(cpuPath, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return fmt.Errorf("failed to open file %s: %v", cpuPath, err) + } + if _, err = f.Write([]byte{'1'}); err != nil { + return fmt.Errorf("failed to write '1' to %s: %v", cpuPath, err) + } + return nil +} + +// Disable turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online. +func (t Thread) Disable() error { + // The core labeled "cpu0" can never be taken offline via this method. + // Linux will return EPERM if the user even creates a file at the /sys + // path above. + if t.processorNumber == 0 { + return fmt.Errorf("invalid shutdown operation: cpu0 cannot be disabled") + } + cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) + return ioutil.WriteFile(cpuPath, []byte{'0'}, 0644) } -func (m Mitigate) doReverse(data []byte) error { - set, err := newCPUSetFromPossible(data) +// 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 err + return true } + return len(data) > 0 && data[0] != '0' +} - log.Infof("Reverse mitigate found the following CPUs...") - log.Infof("%s", set) +// 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 + } - enableList := set.getRemainingList() + if other.cpuFamily != t.cpuFamily { + return false + } - 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 fmt.Errorf("error enabling thread: %s err: %v", t, err) + 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 } } - log.Infof("Enable successful.") - return nil + 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 } diff --git a/runsc/mitigate/mitigate_conf.go b/runsc/mitigate/mitigate_conf.go deleted file mode 100644 index ee326324b..000000000 --- a/runsc/mitigate/mitigate_conf.go +++ /dev/null @@ -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() -} |