summaryrefslogtreecommitdiffhomepage
path: root/runsc/cmd
diff options
context:
space:
mode:
authorZach Koopmans <zkoopmans@google.com>2021-03-11 13:08:11 -0800
committergVisor bot <gvisor-bot@google.com>2021-03-11 13:10:08 -0800
commita82bd04e2ab3230a9ed09b297812b58d00784fe5 (patch)
treecdef0d07c6659b037a59edb3fbe99c62afceb83e /runsc/cmd
parent1020ac83f47cd6b178e7655f413fcd4f3cd2aa4c (diff)
Major refactor of runsc mitigate.
PiperOrigin-RevId: 362360425
Diffstat (limited to 'runsc/cmd')
-rw-r--r--runsc/cmd/BUILD3
-rw-r--r--runsc/cmd/mitigate.go122
-rw-r--r--runsc/cmd/mitigate_test.go169
3 files changed, 287 insertions, 7 deletions
diff --git a/runsc/cmd/BUILD b/runsc/cmd/BUILD
index e3e289da3..2c3b4058b 100644
--- a/runsc/cmd/BUILD
+++ b/runsc/cmd/BUILD
@@ -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",
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/cmd/mitigate_test.go b/runsc/cmd/mitigate_test.go
new file mode 100644
index 000000000..163fece42
--- /dev/null
+++ b/runsc/cmd/mitigate_test.go
@@ -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
+}