github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/cmd/mitigate.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package cmd
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"runtime"
    22  
    23  	"github.com/google/subcommands"
    24  	"github.com/SagerNet/gvisor/pkg/log"
    25  	"github.com/SagerNet/gvisor/runsc/flag"
    26  	"github.com/SagerNet/gvisor/runsc/mitigate"
    27  )
    28  
    29  const (
    30  	// cpuInfo is the path used to parse CPU info.
    31  	cpuInfo = "/proc/cpuinfo"
    32  	// allPossibleCPUs is the path used to enable CPUs.
    33  	allPossibleCPUs = "/sys/devices/system/cpu/possible"
    34  )
    35  
    36  // Mitigate implements subcommands.Command for the "mitigate" command.
    37  type Mitigate struct {
    38  	// Run the command without changing the underlying system.
    39  	dryRun bool
    40  	// Reverse mitigate by turning on all CPU cores.
    41  	reverse bool
    42  	// Path to file to read to create CPUSet.
    43  	path string
    44  	// Extra data for post mitigate operations.
    45  	data string
    46  }
    47  
    48  // Name implements subcommands.command.name.
    49  func (*Mitigate) Name() string {
    50  	return "mitigate"
    51  }
    52  
    53  // Synopsis implements subcommands.Command.Synopsis.
    54  func (*Mitigate) Synopsis() string {
    55  	return "mitigate mitigates the underlying system against side channel attacks"
    56  }
    57  
    58  // Usage implements Usage for cmd.Mitigate.
    59  func (m Mitigate) Usage() string {
    60  	return fmt.Sprintf(`mitigate [flags]
    61  
    62  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.
    63  
    64  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.%s`, m.usage())
    65  }
    66  
    67  // SetFlags sets flags for the command Mitigate.
    68  func (m *Mitigate) SetFlags(f *flag.FlagSet) {
    69  	f.BoolVar(&m.dryRun, "dryrun", false, "run the command without changing system")
    70  	f.BoolVar(&m.reverse, "reverse", false, "reverse mitigate by enabling all CPUs")
    71  	m.setFlags(f)
    72  }
    73  
    74  // Execute implements subcommands.Command.Execute.
    75  func (m *Mitigate) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
    76  	if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" {
    77  		log.Warningf("As ARM is not affected by MDS, mitigate does not support")
    78  		return subcommands.ExitFailure
    79  	}
    80  
    81  	if f.NArg() != 0 {
    82  		f.Usage()
    83  		return subcommands.ExitUsageError
    84  	}
    85  
    86  	m.path = cpuInfo
    87  	if m.reverse {
    88  		m.path = allPossibleCPUs
    89  	}
    90  
    91  	set, err := m.doExecute()
    92  	if err != nil {
    93  		return Errorf("Execute failed: %v", err)
    94  	}
    95  
    96  	if m.data == "" {
    97  		return subcommands.ExitSuccess
    98  	}
    99  
   100  	if err = m.postMitigate(set); err != nil {
   101  		return Errorf("Post Mitigate failed: %v", err)
   102  	}
   103  
   104  	return subcommands.ExitSuccess
   105  }
   106  
   107  // Execute executes the Mitigate command.
   108  func (m *Mitigate) doExecute() (mitigate.CPUSet, error) {
   109  	if m.dryRun {
   110  		log.Infof("Running with DryRun. No cpu settings will be changed.")
   111  	}
   112  	data, err := ioutil.ReadFile(m.path)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("failed to read %s: %w", m.path, err)
   115  	}
   116  	if m.reverse {
   117  		set, err := m.doReverse(data)
   118  		if err != nil {
   119  			return nil, fmt.Errorf("reverse operation failed: %w", err)
   120  		}
   121  		return set, nil
   122  	}
   123  	set, err := m.doMitigate(data)
   124  	if err != nil {
   125  		return nil, fmt.Errorf("mitigate operation failed: %w", err)
   126  	}
   127  	return set, nil
   128  }
   129  
   130  func (m *Mitigate) doMitigate(data []byte) (mitigate.CPUSet, error) {
   131  	set, err := mitigate.NewCPUSet(data)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	log.Infof("Mitigate found the following CPUs...")
   137  	log.Infof("%s", set)
   138  
   139  	disableList := set.GetShutdownList()
   140  	log.Infof("Disabling threads on thread pairs.")
   141  	for _, t := range disableList {
   142  		log.Infof("Disable thread: %s", t)
   143  		if m.dryRun {
   144  			continue
   145  		}
   146  		if err := t.Disable(); err != nil {
   147  			return nil, fmt.Errorf("error disabling thread: %s err: %w", t, err)
   148  		}
   149  	}
   150  	log.Infof("Shutdown successful.")
   151  	return set, nil
   152  }
   153  
   154  func (m *Mitigate) doReverse(data []byte) (mitigate.CPUSet, error) {
   155  	set, err := mitigate.NewCPUSetFromPossible(data)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  
   160  	log.Infof("Reverse mitigate found the following CPUs...")
   161  	log.Infof("%s", set)
   162  
   163  	enableList := set.GetRemainingList()
   164  
   165  	log.Infof("Enabling all CPUs...")
   166  	for _, t := range enableList {
   167  		log.Infof("Enabling thread: %s", t)
   168  		if m.dryRun {
   169  			continue
   170  		}
   171  		if err := t.Enable(); err != nil {
   172  			return nil, fmt.Errorf("error enabling thread: %s err: %w", t, err)
   173  		}
   174  	}
   175  	log.Infof("Enable successful.")
   176  	return set, nil
   177  }