github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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 "os" 22 "runtime" 23 24 "github.com/google/subcommands" 25 "github.com/metacubex/gvisor/pkg/log" 26 "github.com/metacubex/gvisor/runsc/cmd/util" 27 "github.com/metacubex/gvisor/runsc/flag" 28 "github.com/metacubex/gvisor/runsc/mitigate" 29 ) 30 31 const ( 32 // cpuInfo is the path used to parse CPU info. 33 cpuInfo = "/proc/cpuinfo" 34 // Path to enable/disable SMT. 35 smtPath = "/sys/devices/system/cpu/smt/control" 36 ) 37 38 // Mitigate implements subcommands.Command for the "mitigate" command. 39 type Mitigate struct { 40 // Run the command without changing the underlying system. 41 dryRun bool 42 // Reverse mitigate by turning on all CPU cores. 43 reverse bool 44 // Extra data for post mitigate operations. 45 data string 46 // Control to mitigate/reverse smt. 47 control machineControl 48 } 49 50 // Name implements subcommands.command.name. 51 func (*Mitigate) Name() string { 52 return "mitigate" 53 } 54 55 // Synopsis implements subcommands.Command.Synopsis. 56 func (*Mitigate) Synopsis() string { 57 return "mitigate mitigates the underlying system against side channel attacks" 58 } 59 60 // Usage implements Usage for cmd.Mitigate. 61 func (m *Mitigate) Usage() string { 62 return fmt.Sprintf(`mitigate [flags] 63 64 mitigate mitigates a system to the "MDS" vulnerability by writing "off" to %q. CPUs can be restored by writing "on" to the same file or rebooting your system. 65 66 The command can be reversed with --reverse, which writes "on" to the file above.%s`, smtPath, m.usage()) 67 } 68 69 // SetFlags sets flags for the command Mitigate. 70 func (m *Mitigate) SetFlags(f *flag.FlagSet) { 71 f.BoolVar(&m.dryRun, "dryrun", false, "run the command without changing system") 72 f.BoolVar(&m.reverse, "reverse", false, "reverse mitigate by enabling all CPUs") 73 m.setFlags(f) 74 } 75 76 // Execute implements subcommands.Command.Execute. 77 func (m *Mitigate) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommands.ExitStatus { 78 if runtime.GOARCH == "arm64" || runtime.GOARCH == "arm" { 79 log.Warningf("As ARM is not affected by MDS, mitigate does not support ARM machines.") 80 // Set reverse flag so that we still perform post mitigate operations. mitigate reverse is a noop in this case. 81 m.reverse = true 82 } 83 84 if f.NArg() != 0 { 85 f.Usage() 86 return subcommands.ExitUsageError 87 } 88 m.control = &machineControlImpl{} 89 return m.execute() 90 } 91 92 // execute executes mitigate operations. Seperate from Execute method for 93 // easier mocking. 94 func (m *Mitigate) execute() subcommands.ExitStatus { 95 beforeSet, err := m.control.getCPUs() 96 if err != nil { 97 return util.Errorf("Get before CPUSet failed: %v", err) 98 } 99 log.Infof("CPUs before: %s", beforeSet.String()) 100 101 if err := m.doEnableDisable(beforeSet); err != nil { 102 return util.Errorf("Enabled/Disable action failed on %q: %v", smtPath, err) 103 } 104 105 afterSet, err := m.control.getCPUs() 106 if err != nil { 107 return util.Errorf("Get after CPUSet failed: %v", err) 108 } 109 log.Infof("CPUs after: %s", afterSet.String()) 110 111 if err = m.postMitigate(afterSet); err != nil { 112 return util.Errorf("Post Mitigate failed: %v", err) 113 } 114 115 return subcommands.ExitSuccess 116 } 117 118 // doEnableDisable does either enable or disable operation based on flags. 119 func (m *Mitigate) doEnableDisable(set mitigate.CPUSet) error { 120 if m.reverse { 121 if m.dryRun { 122 log.Infof("Skipping reverse action because dryrun is set.") 123 return nil 124 } 125 return m.control.enable() 126 } 127 if m.dryRun { 128 log.Infof("Skipping mitigate action because dryrun is set.") 129 return nil 130 } 131 if set.IsVulnerable() { 132 return m.control.disable() 133 } 134 log.Infof("CPUs not vulnerable. Skipping disable call.") 135 return nil 136 } 137 138 // Interface to wrap interactions with underlying machine. Done 139 // so testing with mocks can be done hermetically. 140 type machineControl interface { 141 enable() error 142 disable() error 143 isEnabled() (bool, error) 144 getCPUs() (mitigate.CPUSet, error) 145 } 146 147 // Implementation of SMT control interaction with the underlying machine. 148 type machineControlImpl struct{} 149 150 func (*machineControlImpl) enable() error { 151 return checkFileExistsOnWrite("enable", "on") 152 } 153 154 func (*machineControlImpl) disable() error { 155 return checkFileExistsOnWrite("disable", "off") 156 } 157 158 // Writes data to SMT control. If file not found, logs file not exist error and returns nil 159 // error, which is done because machines without the file pointed to by smtPath only have one 160 // thread per core in the first place. Otherwise returns error from ioutil.WriteFile. 161 func checkFileExistsOnWrite(op, data string) error { 162 err := ioutil.WriteFile(smtPath, []byte(data), 0644) 163 if err != nil && os.IsExist(err) { 164 log.Infof("File %q does not exist for operation %s. This machine probably has no smt control.", smtPath, op) 165 return nil 166 } 167 return err 168 } 169 170 func (*machineControlImpl) isEnabled() (bool, error) { 171 data, err := ioutil.ReadFile(cpuInfo) 172 return string(data) == "on", err 173 } 174 175 func (*machineControlImpl) getCPUs() (mitigate.CPUSet, error) { 176 data, err := ioutil.ReadFile(cpuInfo) 177 if err != nil { 178 return nil, fmt.Errorf("failed to read %s: %w", cpuInfo, err) 179 } 180 set, err := mitigate.NewCPUSet(string(data)) 181 if err != nil { 182 return nil, fmt.Errorf("getCPUs: %v", err) 183 } 184 return set, nil 185 }