summaryrefslogtreecommitdiffhomepage
path: root/runsc/mitigate/cpu.go
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/mitigate/cpu.go')
-rw-r--r--runsc/mitigate/cpu.go235
1 files changed, 235 insertions, 0 deletions
diff --git a/runsc/mitigate/cpu.go b/runsc/mitigate/cpu.go
new file mode 100644
index 000000000..113b98159
--- /dev/null
+++ b/runsc/mitigate/cpu.go
@@ -0,0 +1,235 @@
+// 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"
+ "regexp"
+ "strconv"
+ "strings"
+)
+
+const (
+ // constants of coomm
+ meltdown = "cpu_meltdown"
+ l1tf = "l1tf"
+ mds = "mds"
+ swapgs = "swapgs"
+ taa = "taa"
+)
+
+const (
+ processorKey = "processor"
+ vendorIDKey = "vendor_id"
+ cpuFamilyKey = "cpu family"
+ modelKey = "model"
+ coreIDKey = "core id"
+ bugsKey = "bugs"
+)
+
+// getCPUSet returns cpu structs from reading /proc/cpuinfo.
+func getCPUSet(data string) ([]*cpu, 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: %s", 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.
+ var cpus = make([]*cpu, 0, len(indices)-1)
+ // 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 := getCPU(data[start:end])
+ if err != nil {
+ return nil, err
+ }
+ cpus = append(cpus, c)
+ }
+ return cpus, nil
+}
+
+// type cpu represents pertinent info about a cpu.
+type cpu 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).
+ coreID int64 // This CPU's core id to match Hyperthread Pairs
+ bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field.
+}
+
+// getCPU parses a CPU from a single cpu entry from /proc/cpuinfo.
+func getCPU(data string) (*cpu, error) {
+ processor, err := parseProcessor(data)
+ if err != nil {
+ return nil, err
+ }
+
+ vendorID, err := parseVendorID(data)
+ if err != nil {
+ return nil, err
+ }
+
+ cpuFamily, err := parseCPUFamily(data)
+ if err != nil {
+ return nil, err
+ }
+
+ model, err := parseModel(data)
+ if err != nil {
+ return nil, err
+ }
+
+ coreID, err := parseCoreID(data)
+ if err != nil {
+ return nil, err
+ }
+
+ bugs, err := parseBugs(data)
+ if err != nil {
+ return nil, err
+ }
+
+ return &cpu{
+ processorNumber: processor,
+ vendorID: vendorID,
+ cpuFamily: cpuFamily,
+ model: model,
+ coreID: coreID,
+ bugs: bugs,
+ }, nil
+}
+
+// List of pertinent side channel vulnerablilites.
+// For mds, see: https://www.kernel.org/doc/html/latest/admin-guide/hw-vuln/mds.html.
+var vulnerabilities = []string{
+ meltdown,
+ l1tf,
+ mds,
+ swapgs,
+ taa,
+}
+
+// isVulnerable checks if a CPU is vulnerable to pertinent bugs.
+func (c *cpu) isVulnerable() bool {
+ for _, bug := range vulnerabilities {
+ if _, ok := c.bugs[bug]; ok {
+ return true
+ }
+ }
+ return false
+}
+
+// similarTo checks family/model/bugs fields for equality of two
+// processors.
+func (c *cpu) similarTo(other *cpu) bool {
+ if c.vendorID != other.vendorID {
+ return false
+ }
+
+ if other.cpuFamily != c.cpuFamily {
+ return false
+ }
+
+ if other.model != c.model {
+ return false
+ }
+
+ if len(other.bugs) != len(c.bugs) {
+ return false
+ }
+
+ for bug := range c.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)
+}
+
+// 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 %s: %s", key, data)
+ }
+ return matches[1], nil
+}