diff options
-rw-r--r-- | runsc/cmd/mitigate.go | 10 | ||||
-rw-r--r-- | runsc/mitigate/cpu.go | 118 | ||||
-rw-r--r-- | runsc/mitigate/mitigate.go | 82 | ||||
-rw-r--r-- | runsc/mitigate/mitigate_conf.go | 2 |
4 files changed, 165 insertions, 47 deletions
diff --git a/runsc/cmd/mitigate.go b/runsc/cmd/mitigate.go index 9052f091d..822af1917 100644 --- a/runsc/cmd/mitigate.go +++ b/runsc/cmd/mitigate.go @@ -16,7 +16,6 @@ package cmd import ( "context" - "io/ioutil" "github.com/google/subcommands" "gvisor.dev/gvisor/pkg/log" @@ -56,14 +55,7 @@ func (m *Mitigate) Execute(_ context.Context, f *flag.FlagSet, args ...interface return subcommands.ExitUsageError } - const path = "/proc/cpuinfo" - data, err := ioutil.ReadFile(path) - if err != nil { - log.Warningf("Failed to read %s: %v", path, err) - return subcommands.ExitFailure - } - - if err := m.mitigate.Execute(data); err != nil { + if err := m.mitigate.Execute(); err != nil { log.Warningf("Execute failed: %v", err) return subcommands.ExitFailure } diff --git a/runsc/mitigate/cpu.go b/runsc/mitigate/cpu.go index 38f9b787a..4b2aa351f 100644 --- a/runsc/mitigate/cpu.go +++ b/runsc/mitigate/cpu.go @@ -45,7 +45,7 @@ const ( 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) { +func newCPUSet(data []byte, vulnerable func(thread) bool) (cpuSet, error) { processors, err := getThreads(string(data)) if err != nil { return nil, err @@ -68,6 +68,26 @@ func newCPUSet(data []byte, vulnerable func(*thread) bool) (cpuSet, error) { 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 := "" @@ -79,8 +99,8 @@ func (c cpuSet) String() string { // getRemainingList returns the list of threads that will remain active // after mitigation. -func (c cpuSet) getRemainingList() []*thread { - threads := make([]*thread, 0, len(c)) +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 { @@ -95,8 +115,8 @@ func (c cpuSet) getRemainingList() []*thread { // getShutdownList returns the list of threads that will be shutdown on // mitigation. -func (c cpuSet) getShutdownList() []*thread { - threads := make([]*thread, 0) +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. @@ -109,12 +129,12 @@ func (c cpuSet) getShutdownList() []*thread { // threadGroup represents Hyperthread pairs on the same physical/core ID. type threadGroup struct { - threads []*thread + threads []thread isVulnerable bool } // String implements the String method for threadGroup. -func (c *threadGroup) String() string { +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) @@ -123,13 +143,13 @@ func (c *threadGroup) String() string { } // getThreads returns threads structs from reading /proc/cpuinfo. -func getThreads(data string) ([]*thread, error) { +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: %s", data) + return nil, fmt.Errorf("no cpus found for: %q", data) } // Add the ending index for last entry. @@ -139,7 +159,7 @@ func getThreads(data string) ([]*thread, error) { // 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([]*thread, 0, len(indices)-1) + 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] @@ -154,6 +174,45 @@ func getThreads(data string) ([]*thread, error) { 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 { @@ -172,43 +231,44 @@ type thread struct { } // newThread parses a CPU from a single cpu entry from /proc/cpuinfo. -func newThread(data string) (*thread, error) { +func newThread(data string) (thread, error) { + empty := thread{} processor, err := parseProcessor(data) if err != nil { - return nil, err + return empty, err } vendorID, err := parseVendorID(data) if err != nil { - return nil, err + return empty, err } cpuFamily, err := parseCPUFamily(data) if err != nil { - return nil, err + return empty, err } model, err := parseModel(data) if err != nil { - return nil, err + return empty, err } physicalID, err := parsePhysicalID(data) if err != nil { - return nil, err + return empty, err } coreID, err := parseCoreID(data) if err != nil { - return nil, err + return empty, err } bugs, err := parseBugs(data) if err != nil { - return nil, err + return empty, err } - return &thread{ + return thread{ processorNumber: processor, vendorID: vendorID, cpuFamily: cpuFamily, @@ -222,7 +282,7 @@ func newThread(data string) (*thread, error) { } // String implements the String method for thread. -func (t *thread) String() string { +func (t thread) String() string { template := `CPU: %d CPU ID: %+v Vendor: %s @@ -237,21 +297,27 @@ Bugs: %s return fmt.Sprintf(template, t.processorNumber, t.id, t.vendorID, t.cpuFamily, t.model, strings.Join(bugs, ",")) } -// shutdown turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online. -func (t *thread) shutdown() error { +// 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 { +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 { +func (t thread) isActive() bool { cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) data, err := ioutil.ReadFile(cpuPath) if err != nil { @@ -262,7 +328,7 @@ func (t *thread) isActive() bool { // similarTo checks family/model/bugs fields for equality of two // processors. -func (t *thread) similarTo(other *thread) bool { +func (t thread) similarTo(other thread) bool { if t.vendorID != other.vendorID { return false } @@ -351,7 +417,7 @@ 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 "", 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 3ea58454f..91de623e3 100644 --- a/runsc/mitigate/mitigate.go +++ b/runsc/mitigate/mitigate.go @@ -21,15 +21,23 @@ package mitigate import ( "fmt" + "io/ioutil" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/flag" ) +const ( + cpuInfo = "/proc/cpuinfo" + allPossibleCPUs = "/sys/devices/system/cpu/possible" +) + // Mitigate handles high level mitigate operations provided to runsc. type Mitigate struct { - dryRun bool // Run the command without changing the underlying system. - other mitigate // Struct holds extra mitigate logic. + 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). } // Usage implments Usage for cmd.Mitigate. @@ -37,6 +45,8 @@ func (m Mitigate) Usage() string { usageString := `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. ` return usageString + m.other.usage() } @@ -44,31 +54,81 @@ Mitigate mitigates a system to the "MDS" vulnerability by implementing a manual // 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 + } } // Execute executes the Mitigate command. -func (m Mitigate) Execute(data []byte) error { +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) + } + + if m.reverse { + err := m.doReverse(data) + if err != nil { + return fmt.Errorf("reverse operation failed: %v", err) + } + return nil + } + + set, err := m.doMitigate(data) + if err != nil { + return fmt.Errorf("mitigate operation failed: %v", err) + } + return m.other.execute(set, m.dryRun) +} + +func (m Mitigate) doMitigate(data []byte) (cpuSet, error) { set, err := newCPUSet(data, m.other.vulnerable) if err != nil { - return err + return nil, err } log.Infof("Mitigate found the following CPUs...") log.Infof("%s", set) - shutdownList := set.getShutdownList() - log.Infof("Shutting down threads on thread pairs.") - for _, t := range shutdownList { - log.Infof("Shutting down thread: %s", t) + 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.shutdown(); err != nil { - return fmt.Errorf("error shutting down thread: %s err: %v", t, err) + if err := t.disable(); err != nil { + return nil, fmt.Errorf("error disabling thread: %s err: %v", t, err) } } log.Infof("Shutdown successful.") - m.other.execute(set, m.dryRun) + return set, nil +} + +func (m Mitigate) doReverse(data []byte) error { + set, err := newCPUSetFromPossible(data) + if err != nil { + return 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 fmt.Errorf("error enabling thread: %s err: %v", t, err) + } + } + log.Infof("Enable successful.") return nil } diff --git a/runsc/mitigate/mitigate_conf.go b/runsc/mitigate/mitigate_conf.go index 1e74f5891..ee326324b 100644 --- a/runsc/mitigate/mitigate_conf.go +++ b/runsc/mitigate/mitigate_conf.go @@ -32,6 +32,6 @@ func (m mitigate) execute(set cpuSet, dryrun bool) error { return nil } -func (m mitigate) vulnerable(other *thread) bool { +func (m mitigate) vulnerable(other thread) bool { return other.isVulnerable() } |