diff options
Diffstat (limited to 'runsc/cmd')
-rw-r--r-- | runsc/cmd/BUILD | 3 | ||||
-rw-r--r-- | runsc/cmd/boot.go | 5 | ||||
-rw-r--r-- | runsc/cmd/checkpoint.go | 4 | ||||
-rw-r--r-- | runsc/cmd/chroot.go | 16 | ||||
-rw-r--r-- | runsc/cmd/cmd.go | 10 | ||||
-rw-r--r-- | runsc/cmd/debug.go | 6 | ||||
-rw-r--r-- | runsc/cmd/do.go | 6 | ||||
-rw-r--r-- | runsc/cmd/exec.go | 12 | ||||
-rw-r--r-- | runsc/cmd/gofer.go | 43 | ||||
-rw-r--r-- | runsc/cmd/kill.go | 7 | ||||
-rw-r--r-- | runsc/cmd/mitigate.go | 122 | ||||
-rw-r--r-- | runsc/cmd/mitigate_test.go | 169 | ||||
-rw-r--r-- | runsc/cmd/restore.go | 4 | ||||
-rw-r--r-- | runsc/cmd/run.go | 4 | ||||
-rw-r--r-- | runsc/cmd/wait.go | 6 |
15 files changed, 348 insertions, 69 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/boot.go b/runsc/cmd/boot.go index 2c92e3067..a14249641 100644 --- a/runsc/cmd/boot.go +++ b/runsc/cmd/boot.go @@ -19,7 +19,6 @@ import ( "os" "runtime/debug" "strings" - "syscall" "github.com/google/subcommands" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -259,8 +258,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ws := l.WaitExit() log.Infof("application exiting with %+v", ws) - waitStatus := args[1].(*syscall.WaitStatus) - *waitStatus = syscall.WaitStatus(ws.Status()) + waitStatus := args[1].(*unix.WaitStatus) + *waitStatus = unix.WaitStatus(ws.Status()) l.Destroy() return subcommands.ExitSuccess } diff --git a/runsc/cmd/checkpoint.go b/runsc/cmd/checkpoint.go index 124198239..a9dbe86de 100644 --- a/runsc/cmd/checkpoint.go +++ b/runsc/cmd/checkpoint.go @@ -18,9 +18,9 @@ import ( "context" "os" "path/filepath" - "syscall" "github.com/google/subcommands" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" @@ -73,7 +73,7 @@ func (c *Checkpoint) Execute(_ context.Context, f *flag.FlagSet, args ...interfa id := f.Arg(0) conf := args[0].(*config.Config) - waitStatus := args[1].(*syscall.WaitStatus) + waitStatus := args[1].(*unix.WaitStatus) cont, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{}) if err != nil { diff --git a/runsc/cmd/chroot.go b/runsc/cmd/chroot.go index 189244765..e988247da 100644 --- a/runsc/cmd/chroot.go +++ b/runsc/cmd/chroot.go @@ -18,8 +18,8 @@ import ( "fmt" "os" "path/filepath" - "syscall" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/specutils" ) @@ -49,11 +49,11 @@ func pivotRoot(root string) error { // will be moved to "/" too. The parent mount of the old_root will be // new_root, so after umounting the old_root, we will see only // the new_root in "/". - if err := syscall.PivotRoot(".", "."); err != nil { + if err := unix.PivotRoot(".", "."); err != nil { return fmt.Errorf("pivot_root failed, make sure that the root mount has a parent: %v", err) } - if err := syscall.Unmount(".", syscall.MNT_DETACH); err != nil { + if err := unix.Unmount(".", unix.MNT_DETACH); err != nil { return fmt.Errorf("error umounting the old root file system: %v", err) } return nil @@ -70,26 +70,26 @@ func setUpChroot(pidns bool) error { // Convert all shared mounts into slave to be sure that nothing will be // propagated outside of our namespace. - if err := syscall.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { + if err := unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { return fmt.Errorf("error converting mounts: %v", err) } - if err := syscall.Mount("runsc-root", chroot, "tmpfs", syscall.MS_NOSUID|syscall.MS_NODEV|syscall.MS_NOEXEC, ""); err != nil { + if err := unix.Mount("runsc-root", chroot, "tmpfs", unix.MS_NOSUID|unix.MS_NODEV|unix.MS_NOEXEC, ""); err != nil { return fmt.Errorf("error mounting tmpfs in choot: %v", err) } if pidns { - flags := uint32(syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC | syscall.MS_RDONLY) + flags := uint32(unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC | unix.MS_RDONLY) if err := mountInChroot(chroot, "proc", "/proc", "proc", flags); err != nil { return fmt.Errorf("error mounting proc in chroot: %v", err) } } else { - if err := mountInChroot(chroot, "/proc", "/proc", "bind", syscall.MS_BIND|syscall.MS_RDONLY|syscall.MS_REC); err != nil { + if err := mountInChroot(chroot, "/proc", "/proc", "bind", unix.MS_BIND|unix.MS_RDONLY|unix.MS_REC); err != nil { return fmt.Errorf("error mounting proc in chroot: %v", err) } } - if err := syscall.Mount("", chroot, "", syscall.MS_REMOUNT|syscall.MS_RDONLY|syscall.MS_BIND, ""); err != nil { + if err := unix.Mount("", chroot, "", unix.MS_REMOUNT|unix.MS_RDONLY|unix.MS_BIND, ""); err != nil { return fmt.Errorf("error remounting chroot in read-only: %v", err) } diff --git a/runsc/cmd/cmd.go b/runsc/cmd/cmd.go index f1a4887ef..4dd55cc33 100644 --- a/runsc/cmd/cmd.go +++ b/runsc/cmd/cmd.go @@ -19,9 +19,9 @@ import ( "fmt" "runtime" "strconv" - "syscall" specs "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/specutils" ) @@ -71,7 +71,7 @@ func setCapsAndCallSelf(args []string, caps *specs.LinuxCapabilities) error { binPath := specutils.ExePath log.Infof("Execve %q again, bye!", binPath) - err := syscall.Exec(binPath, args, []string{}) + err := unix.Exec(binPath, args, []string{}) return fmt.Errorf("error executing %s: %v", binPath, err) } @@ -83,16 +83,16 @@ func callSelfAsNobody(args []string) error { const nobody = 65534 - if _, _, err := syscall.RawSyscall(syscall.SYS_SETGID, uintptr(nobody), 0, 0); err != 0 { + if _, _, err := unix.RawSyscall(unix.SYS_SETGID, uintptr(nobody), 0, 0); err != 0 { return fmt.Errorf("error setting uid: %v", err) } - if _, _, err := syscall.RawSyscall(syscall.SYS_SETUID, uintptr(nobody), 0, 0); err != 0 { + if _, _, err := unix.RawSyscall(unix.SYS_SETUID, uintptr(nobody), 0, 0); err != 0 { return fmt.Errorf("error setting gid: %v", err) } binPath := specutils.ExePath log.Infof("Execve %q again, bye!", binPath) - err := syscall.Exec(binPath, args, []string{}) + err := unix.Exec(binPath, args, []string{}) return fmt.Errorf("error executing %s: %v", binPath, err) } diff --git a/runsc/cmd/debug.go b/runsc/cmd/debug.go index b84142b0d..6212ffb2e 100644 --- a/runsc/cmd/debug.go +++ b/runsc/cmd/debug.go @@ -21,10 +21,10 @@ import ( "strconv" "strings" "sync" - "syscall" "time" "github.com/google/subcommands" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/control" "gvisor.dev/gvisor/runsc/config" @@ -135,7 +135,7 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // Perform synchronous actions. if d.signal > 0 { log.Infof("Sending signal %d to process: %d", d.signal, c.Sandbox.Pid) - if err := syscall.Kill(c.Sandbox.Pid, syscall.Signal(d.signal)); err != nil { + if err := unix.Kill(c.Sandbox.Pid, unix.Signal(d.signal)); err != nil { return Errorf("failed to send signal %d to processs %d", d.signal, c.Sandbox.Pid) } } @@ -317,7 +317,7 @@ func (d *Debug) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) wg.Wait() }() signals := make(chan os.Signal, 1) - signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT) + signal.Notify(signals, unix.SIGTERM, unix.SIGINT) select { case <-readyChan: break // Safe to proceed. diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index 8a8d9f752..22c1dfeb8 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -26,10 +26,10 @@ import ( "path/filepath" "strconv" "strings" - "syscall" "github.com/google/subcommands" specs "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" @@ -86,7 +86,7 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su } conf := args[0].(*config.Config) - waitStatus := args[1].(*syscall.WaitStatus) + waitStatus := args[1].(*unix.WaitStatus) if conf.Rootless { if err := specutils.MaybeRunAsRoot(); err != nil { @@ -225,7 +225,7 @@ func resolvePath(path string) (string, error) { return "", fmt.Errorf("resolving %q: %v", path, err) } path = filepath.Clean(path) - if err := syscall.Access(path, 0); err != nil { + if err := unix.Access(path, 0); err != nil { return "", fmt.Errorf("unable to access %q: %v", path, err) } return path, nil diff --git a/runsc/cmd/exec.go b/runsc/cmd/exec.go index e9726401a..242d474b8 100644 --- a/runsc/cmd/exec.go +++ b/runsc/cmd/exec.go @@ -24,11 +24,11 @@ import ( "path/filepath" "strconv" "strings" - "syscall" "time" "github.com/google/subcommands" specs "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/sentry/control" "gvisor.dev/gvisor/pkg/sentry/kernel/auth" @@ -110,7 +110,7 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) if err != nil { Fatalf("parsing process spec: %v", err) } - waitStatus := args[1].(*syscall.WaitStatus) + waitStatus := args[1].(*unix.WaitStatus) c, err := container.Load(conf.RootDir, container.FullID{ContainerID: id}, container.LoadOpts{}) if err != nil { @@ -149,7 +149,7 @@ func (ex *Exec) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) return ex.exec(c, e, waitStatus) } -func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *syscall.WaitStatus) subcommands.ExitStatus { +func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *unix.WaitStatus) subcommands.ExitStatus { // Start the new process and get its pid. pid, err := c.Execute(e) if err != nil { @@ -189,7 +189,7 @@ func (ex *Exec) exec(c *container.Container, e *control.ExecArgs, waitStatus *sy return subcommands.ExitSuccess } -func (ex *Exec) execChildAndWait(waitStatus *syscall.WaitStatus) subcommands.ExitStatus { +func (ex *Exec) execChildAndWait(waitStatus *unix.WaitStatus) subcommands.ExitStatus { var args []string for _, a := range os.Args[1:] { if !strings.Contains(a, "detach") { @@ -233,7 +233,7 @@ func (ex *Exec) execChildAndWait(waitStatus *syscall.WaitStatus) subcommands.Exi cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty - cmd.SysProcAttr = &syscall.SysProcAttr{ + cmd.SysProcAttr = &unix.SysProcAttr{ Setsid: true, Setctty: true, // The Ctty FD must be the FD in the child process's FD @@ -263,7 +263,7 @@ func (ex *Exec) execChildAndWait(waitStatus *syscall.WaitStatus) subcommands.Exi } return pid == cmd.Process.Pid, nil } - if pe, ok := err.(*os.PathError); !ok || pe.Err != syscall.ENOENT { + if pe, ok := err.(*os.PathError); !ok || pe.Err != unix.ENOENT { return false, err } // No file yet, continue to wait... diff --git a/runsc/cmd/gofer.go b/runsc/cmd/gofer.go index 371fcc0ae..639b2219c 100644 --- a/runsc/cmd/gofer.go +++ b/runsc/cmd/gofer.go @@ -21,7 +21,6 @@ import ( "os" "path/filepath" "strings" - "syscall" "github.com/google/subcommands" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -149,16 +148,16 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // fsgofer should run with a umask of 0, because we want to preserve file // modes exactly as sent by the sandbox, which will have applied its own umask. - syscall.Umask(0) + unix.Umask(0) if err := fsgofer.OpenProcSelfFD(); err != nil { Fatalf("failed to open /proc/self/fd: %v", err) } - if err := syscall.Chroot(root); err != nil { + if err := unix.Chroot(root); err != nil { Fatalf("failed to chroot to %q: %v", root, err) } - if err := syscall.Chdir("/"); err != nil { + if err := unix.Chdir("/"); err != nil { Fatalf("changing working dir: %v", err) } log.Infof("Process chroot'd to %q", root) @@ -166,7 +165,8 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // Start with root mount, then add any other additional mount as needed. ats := make([]p9.Attacher, 0, len(spec.Mounts)+1) ap, err := fsgofer.NewAttachPoint("/", fsgofer.Config{ - ROMount: spec.Root.Readonly || conf.Overlay, + ROMount: spec.Root.Readonly || conf.Overlay, + EnableXattr: conf.Verity, }) if err != nil { Fatalf("creating attach point: %v", err) @@ -178,8 +178,9 @@ func (g *Gofer) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) for _, m := range spec.Mounts { if specutils.Is9PMount(m) { cfg := fsgofer.Config{ - ROMount: isReadonlyMount(m.Options) || conf.Overlay, - HostUDS: conf.FSGoferHostUDS, + ROMount: isReadonlyMount(m.Options) || conf.Overlay, + HostUDS: conf.FSGoferHostUDS, + EnableXattr: conf.Verity, } ap, err := fsgofer.NewAttachPoint(m.Destination, cfg) if err != nil { @@ -262,7 +263,7 @@ func isReadonlyMount(opts []string) bool { func setupRootFS(spec *specs.Spec, conf *config.Config) error { // Convert all shared mounts into slaves to be sure that nothing will be // propagated outside of our namespace. - if err := syscall.Mount("", "/", "", syscall.MS_SLAVE|syscall.MS_REC, ""); err != nil { + if err := unix.Mount("", "/", "", unix.MS_SLAVE|unix.MS_REC, ""); err != nil { Fatalf("error converting mounts: %v", err) } @@ -274,30 +275,30 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error { // // We need a directory to construct a new root and we know that // runsc can't start without /proc, so we can use it for this. - flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOEXEC) - if err := syscall.Mount("runsc-root", "/proc", "tmpfs", flags, ""); err != nil { + flags := uintptr(unix.MS_NOSUID | unix.MS_NODEV | unix.MS_NOEXEC) + if err := unix.Mount("runsc-root", "/proc", "tmpfs", flags, ""); err != nil { Fatalf("error mounting tmpfs: %v", err) } // Prepare tree structure for pivot_root(2). os.Mkdir("/proc/proc", 0755) os.Mkdir("/proc/root", 0755) - if err := syscall.Mount("runsc-proc", "/proc/proc", "proc", flags|syscall.MS_RDONLY, ""); err != nil { + if err := unix.Mount("runsc-proc", "/proc/proc", "proc", flags|unix.MS_RDONLY, ""); err != nil { Fatalf("error mounting proc: %v", err) } root = "/proc/root" } // Mount root path followed by submounts. - if err := syscall.Mount(spec.Root.Path, root, "bind", syscall.MS_BIND|syscall.MS_REC, ""); err != nil { + if err := unix.Mount(spec.Root.Path, root, "bind", unix.MS_BIND|unix.MS_REC, ""); err != nil { return fmt.Errorf("mounting root on root (%q) err: %v", root, err) } - flags := uint32(syscall.MS_SLAVE | syscall.MS_REC) + flags := uint32(unix.MS_SLAVE | unix.MS_REC) if spec.Linux != nil && spec.Linux.RootfsPropagation != "" { flags = specutils.PropOptionsToFlags([]string{spec.Linux.RootfsPropagation}) } - if err := syscall.Mount("", root, "", uintptr(flags), ""); err != nil { + if err := unix.Mount("", root, "", uintptr(flags), ""); err != nil { return fmt.Errorf("mounting root (%q) with flags: %#x, err: %v", root, flags, err) } @@ -323,8 +324,8 @@ func setupRootFS(spec *specs.Spec, conf *config.Config) error { // If root is a mount point but not read-only, we can change mount options // to make it read-only for extra safety. log.Infof("Remounting root as readonly: %q", root) - flags := uintptr(syscall.MS_BIND | syscall.MS_REMOUNT | syscall.MS_RDONLY | syscall.MS_REC) - if err := syscall.Mount(root, root, "bind", flags, ""); err != nil { + flags := uintptr(unix.MS_BIND | unix.MS_REMOUNT | unix.MS_RDONLY | unix.MS_REC) + if err := unix.Mount(root, root, "bind", flags, ""); err != nil { return fmt.Errorf("remounting root as read-only with source: %q, target: %q, flags: %#x, err: %v", root, root, flags, err) } } @@ -354,10 +355,10 @@ func setupMounts(conf *config.Config, mounts []specs.Mount, root string) error { return fmt.Errorf("resolving symlinks to %q: %v", m.Destination, err) } - flags := specutils.OptionsToFlags(m.Options) | syscall.MS_BIND + flags := specutils.OptionsToFlags(m.Options) | unix.MS_BIND if conf.Overlay { // Force mount read-only if writes are not going to be sent to it. - flags |= syscall.MS_RDONLY + flags |= unix.MS_RDONLY } log.Infof("Mounting src: %q, dst: %q, flags: %#x", m.Source, dst, flags) @@ -368,7 +369,7 @@ func setupMounts(conf *config.Config, mounts []specs.Mount, root string) error { // Set propagation options that cannot be set together with other options. flags = specutils.PropOptionsToFlags(m.Options) if flags != 0 { - if err := syscall.Mount("", dst, "", uintptr(flags), ""); err != nil { + if err := unix.Mount("", dst, "", uintptr(flags), ""); err != nil { return fmt.Errorf("mount dst: %q, flags: %#x, err: %v", dst, flags, err) } } @@ -469,8 +470,8 @@ func adjustMountOptions(conf *config.Config, path string, opts []string) ([]stri copy(rv, opts) if conf.OverlayfsStaleRead { - statfs := syscall.Statfs_t{} - if err := syscall.Statfs(path, &statfs); err != nil { + statfs := unix.Statfs_t{} + if err := unix.Statfs(path, &statfs); err != nil { return nil, err } if statfs.Type == unix.OVERLAYFS_SUPER_MAGIC { diff --git a/runsc/cmd/kill.go b/runsc/cmd/kill.go index e0df39266..239fc7ac2 100644 --- a/runsc/cmd/kill.go +++ b/runsc/cmd/kill.go @@ -19,7 +19,6 @@ import ( "fmt" "strconv" "strings" - "syscall" "github.com/google/subcommands" "golang.org/x/sys/unix" @@ -99,10 +98,10 @@ func (k *Kill) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) return subcommands.ExitSuccess } -func parseSignal(s string) (syscall.Signal, error) { +func parseSignal(s string) (unix.Signal, error) { n, err := strconv.Atoi(s) if err == nil { - sig := syscall.Signal(n) + sig := unix.Signal(n) for _, msig := range signalMap { if sig == msig { return sig, nil @@ -116,7 +115,7 @@ func parseSignal(s string) (syscall.Signal, error) { return -1, fmt.Errorf("unknown signal %q", s) } -var signalMap = map[string]syscall.Signal{ +var signalMap = map[string]unix.Signal{ "ABRT": unix.SIGABRT, "ALRM": unix.SIGALRM, "BUS": unix.SIGBUS, 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 +} diff --git a/runsc/cmd/restore.go b/runsc/cmd/restore.go index 096ec814c..b21f05921 100644 --- a/runsc/cmd/restore.go +++ b/runsc/cmd/restore.go @@ -17,9 +17,9 @@ package cmd import ( "context" "path/filepath" - "syscall" "github.com/google/subcommands" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" "gvisor.dev/gvisor/runsc/flag" @@ -78,7 +78,7 @@ func (r *Restore) Execute(_ context.Context, f *flag.FlagSet, args ...interface{ id := f.Arg(0) conf := args[0].(*config.Config) - waitStatus := args[1].(*syscall.WaitStatus) + waitStatus := args[1].(*unix.WaitStatus) if conf.Rootless { return Errorf("Rootless mode not supported with %q", r.Name()) diff --git a/runsc/cmd/run.go b/runsc/cmd/run.go index c48cbe4cd..722181aff 100644 --- a/runsc/cmd/run.go +++ b/runsc/cmd/run.go @@ -16,9 +16,9 @@ package cmd import ( "context" - "syscall" "github.com/google/subcommands" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" "gvisor.dev/gvisor/runsc/flag" @@ -65,7 +65,7 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s id := f.Arg(0) conf := args[0].(*config.Config) - waitStatus := args[1].(*syscall.WaitStatus) + waitStatus := args[1].(*unix.WaitStatus) if conf.Rootless { return Errorf("Rootless mode not supported with %q", r.Name()) diff --git a/runsc/cmd/wait.go b/runsc/cmd/wait.go index 5d55422c7..d7a783b88 100644 --- a/runsc/cmd/wait.go +++ b/runsc/cmd/wait.go @@ -18,9 +18,9 @@ import ( "context" "encoding/json" "os" - "syscall" "github.com/google/subcommands" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/runsc/config" "gvisor.dev/gvisor/runsc/container" "gvisor.dev/gvisor/runsc/flag" @@ -77,7 +77,7 @@ func (wt *Wait) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) Fatalf("loading container: %v", err) } - var waitStatus syscall.WaitStatus + var waitStatus unix.WaitStatus switch { // Wait on the whole container. case wt.rootPID == unsetPID && wt.pid == unsetPID: @@ -119,7 +119,7 @@ type waitResult struct { // exitStatus returns the correct exit status for a process based on if it // was signaled or exited cleanly. -func exitStatus(status syscall.WaitStatus) int { +func exitStatus(status unix.WaitStatus) int { if status.Signaled() { return 128 + int(status.Signal()) } |