summaryrefslogtreecommitdiffhomepage
path: root/runsc/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'runsc/cmd')
-rw-r--r--runsc/cmd/BUILD3
-rw-r--r--runsc/cmd/boot.go5
-rw-r--r--runsc/cmd/checkpoint.go4
-rw-r--r--runsc/cmd/chroot.go16
-rw-r--r--runsc/cmd/cmd.go10
-rw-r--r--runsc/cmd/debug.go6
-rw-r--r--runsc/cmd/do.go6
-rw-r--r--runsc/cmd/exec.go12
-rw-r--r--runsc/cmd/gofer.go43
-rw-r--r--runsc/cmd/kill.go7
-rw-r--r--runsc/cmd/mitigate.go122
-rw-r--r--runsc/cmd/mitigate_test.go169
-rw-r--r--runsc/cmd/restore.go4
-rw-r--r--runsc/cmd/run.go4
-rw-r--r--runsc/cmd/wait.go6
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())
}