diff options
Diffstat (limited to 'runsc/mitigate/cpu.go')
-rw-r--r-- | runsc/mitigate/cpu.go | 235 |
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 +} |