github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/mitigate/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 mitigate provides libraries for the mitigate command. The 16 // mitigate command mitigates side channel attacks such as MDS. Mitigate 17 // shuts down CPUs via /sys/devices/system/cpu/cpu{N}/online. 18 package mitigate 19 20 import ( 21 "fmt" 22 "io/ioutil" 23 "os" 24 "regexp" 25 "sort" 26 "strconv" 27 "strings" 28 ) 29 30 const ( 31 // mds is the only bug we care about. 32 mds = "mds" 33 34 // Constants for parsing /proc/cpuinfo. 35 processorKey = "processor" 36 vendorIDKey = "vendor_id" 37 cpuFamilyKey = "cpu family" 38 modelKey = "model" 39 physicalIDKey = "physical id" 40 coreIDKey = "core id" 41 bugsKey = "bugs" 42 43 // Path to shutdown a CPU. 44 cpuOnlineTemplate = "/sys/devices/system/cpu/cpu%d/online" 45 ) 46 47 // CPUSet contains a map of all CPUs on the system, mapped 48 // by Physical ID and CoreIDs. threads with the same 49 // Core and Physical ID are Hyperthread pairs. 50 type CPUSet map[threadID]*ThreadGroup 51 52 // NewCPUSet creates a CPUSet from data read from /proc/cpuinfo. 53 func NewCPUSet(data []byte) (CPUSet, error) { 54 processors, err := getThreads(string(data)) 55 if err != nil { 56 return nil, err 57 } 58 59 set := make(CPUSet) 60 for _, p := range processors { 61 // Each ID is of the form physicalID:coreID. Hyperthread pairs 62 // have identical physical and core IDs. We need to match 63 // Hyperthread pairs so that we can shutdown all but one per 64 // pair. 65 core, ok := set[p.id] 66 if !ok { 67 core = &ThreadGroup{} 68 set[p.id] = core 69 } 70 core.isVulnerable = core.isVulnerable || p.IsVulnerable() 71 core.threads = append(core.threads, p) 72 } 73 74 // We need to make sure we shutdown the lowest number processor per 75 // thread group. 76 for _, tg := range set { 77 sort.Slice(tg.threads, func(i, j int) bool { 78 return tg.threads[i].processorNumber < tg.threads[j].processorNumber 79 }) 80 } 81 return set, nil 82 } 83 84 // NewCPUSetFromPossible makes a cpuSet data read from 85 // /sys/devices/system/cpu/possible. This is used in enable operations 86 // where the caller simply wants to enable all CPUS. 87 func NewCPUSetFromPossible(data []byte) (CPUSet, error) { 88 threads, err := GetThreadsFromPossible(data) 89 if err != nil { 90 return nil, err 91 } 92 93 // We don't care if a CPU is vulnerable or not, we just 94 // want to return a list of all CPUs on the host. 95 set := CPUSet{ 96 threads[0].id: &ThreadGroup{ 97 threads: threads, 98 isVulnerable: false, 99 }, 100 } 101 return set, nil 102 } 103 104 // String implements the String method for CPUSet. 105 func (c CPUSet) String() string { 106 ret := "" 107 for _, tg := range c { 108 ret += fmt.Sprintf("%s\n", tg) 109 } 110 return ret 111 } 112 113 // GetRemainingList returns the list of threads that will remain active 114 // after mitigation. 115 func (c CPUSet) GetRemainingList() []Thread { 116 threads := make([]Thread, 0, len(c)) 117 for _, core := range c { 118 // If we're vulnerable, take only one thread from the pair. 119 if core.isVulnerable { 120 threads = append(threads, core.threads[0]) 121 continue 122 } 123 // Otherwise don't shutdown anything. 124 threads = append(threads, core.threads...) 125 } 126 return threads 127 } 128 129 // GetShutdownList returns the list of threads that will be shutdown on 130 // mitigation. 131 func (c CPUSet) GetShutdownList() []Thread { 132 threads := make([]Thread, 0) 133 for _, core := range c { 134 // Only if we're vulnerable do shutdown anything. In this case, 135 // shutdown all but the first entry. 136 if core.isVulnerable && len(core.threads) > 1 { 137 threads = append(threads, core.threads[1:]...) 138 } 139 } 140 return threads 141 } 142 143 // ThreadGroup represents Hyperthread pairs on the same physical/core ID. 144 type ThreadGroup struct { 145 threads []Thread 146 isVulnerable bool 147 } 148 149 // String implements the String method for threadGroup. 150 func (c ThreadGroup) String() string { 151 ret := fmt.Sprintf("ThreadGroup:\nIsVulnerable: %t\n", c.isVulnerable) 152 for _, processor := range c.threads { 153 ret += fmt.Sprintf("%s\n", processor) 154 } 155 return ret 156 } 157 158 // getThreads returns threads structs from reading /proc/cpuinfo. 159 func getThreads(data string) ([]Thread, error) { 160 // Each processor entry should start with the 161 // processor key. Find the beginings of each. 162 r := buildRegex(processorKey, `\d+`) 163 indices := r.FindAllStringIndex(data, -1) 164 if len(indices) < 1 { 165 return nil, fmt.Errorf("no cpus found for: %q", data) 166 } 167 168 // Add the ending index for last entry. 169 indices = append(indices, []int{len(data), -1}) 170 171 // Valid cpus are now defined by strings in between 172 // indexes (e.g. data[index[i], index[i+1]]). 173 // There should be len(indicies) - 1 CPUs 174 // since the last index is the end of the string. 175 cpus := make([]Thread, 0, len(indices)) 176 // Find each string that represents a CPU. These begin "processor". 177 for i := 1; i < len(indices); i++ { 178 start := indices[i-1][0] 179 end := indices[i][0] 180 // Parse the CPU entry, which should be between start/end. 181 c, err := newThread(data[start:end]) 182 if err != nil { 183 return nil, err 184 } 185 cpus = append(cpus, c) 186 } 187 return cpus, nil 188 } 189 190 // GetThreadsFromPossible makes threads from data read from /sys/devices/system/cpu/possible. 191 func GetThreadsFromPossible(data []byte) ([]Thread, error) { 192 possibleRegex := regexp.MustCompile(`(?m)^(\d+)(-(\d+))?$`) 193 matches := possibleRegex.FindStringSubmatch(string(data)) 194 if len(matches) != 4 { 195 return nil, fmt.Errorf("mismatch regex from possible: %q", string(data)) 196 } 197 198 // If matches[3] is empty, we only have one cpu entry. 199 if matches[3] == "" { 200 matches[3] = matches[1] 201 } 202 203 begin, err := strconv.ParseInt(matches[1], 10, 64) 204 if err != nil { 205 return nil, fmt.Errorf("failed to parse begin: %v", err) 206 } 207 end, err := strconv.ParseInt(matches[3], 10, 64) 208 if err != nil { 209 return nil, fmt.Errorf("failed to parse end: %v", err) 210 } 211 if begin > end || begin < 0 || end < 0 { 212 return nil, fmt.Errorf("invalid cpu bounds from possible: begin: %d end: %d", begin, end) 213 } 214 215 ret := make([]Thread, 0, end-begin) 216 for i := begin; i <= end; i++ { 217 ret = append(ret, Thread{ 218 processorNumber: i, 219 id: threadID{ 220 physicalID: 0, // we don't care about id for enable ops. 221 coreID: 0, 222 }, 223 }) 224 } 225 226 return ret, nil 227 } 228 229 // threadID for each thread is defined by the physical and 230 // core IDs. If equal, two threads are Hyperthread pairs. 231 type threadID struct { 232 physicalID int64 233 coreID int64 234 } 235 236 // Thread represents pertinent info about a single hyperthread in a pair. 237 type Thread struct { 238 processorNumber int64 // the processor number of this CPU. 239 vendorID string // the vendorID of CPU (e.g. AuthenticAMD). 240 cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake). 241 model int64 // CPU model number (e.g. 85 for CascadeLake/Skylake). 242 id threadID // id for this thread 243 bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field. 244 } 245 246 // newThread parses a CPU from a single cpu entry from /proc/cpuinfo. 247 func newThread(data string) (Thread, error) { 248 empty := Thread{} 249 processor, err := parseProcessor(data) 250 if err != nil { 251 return empty, err 252 } 253 254 vendorID, err := parseVendorID(data) 255 if err != nil { 256 return empty, err 257 } 258 259 cpuFamily, err := parseCPUFamily(data) 260 if err != nil { 261 return empty, err 262 } 263 264 model, err := parseModel(data) 265 if err != nil { 266 return empty, err 267 } 268 269 physicalID, err := parsePhysicalID(data) 270 if err != nil { 271 return empty, err 272 } 273 274 coreID, err := parseCoreID(data) 275 if err != nil { 276 return empty, err 277 } 278 279 bugs, err := parseBugs(data) 280 if err != nil { 281 return empty, err 282 } 283 284 return Thread{ 285 processorNumber: processor, 286 vendorID: vendorID, 287 cpuFamily: cpuFamily, 288 model: model, 289 id: threadID{ 290 physicalID: physicalID, 291 coreID: coreID, 292 }, 293 bugs: bugs, 294 }, nil 295 } 296 297 // String implements the String method for thread. 298 func (t Thread) String() string { 299 template := `CPU: %d 300 CPU ID: %+v 301 Vendor: %s 302 Family/Model: %d/%d 303 Bugs: %s 304 ` 305 bugs := make([]string, 0) 306 for bug := range t.bugs { 307 bugs = append(bugs, bug) 308 } 309 310 return fmt.Sprintf(template, t.processorNumber, t.id, t.vendorID, t.cpuFamily, t.model, strings.Join(bugs, ",")) 311 } 312 313 // Enable turns on the CPU by writing 1 to /sys/devices/cpu/cpu{N}/online. 314 func (t Thread) Enable() error { 315 // Linux ensures that "cpu0" is always online. 316 if t.processorNumber == 0 { 317 return nil 318 } 319 cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) 320 f, err := os.OpenFile(cpuPath, os.O_WRONLY|os.O_CREATE, 0644) 321 if err != nil { 322 return fmt.Errorf("failed to open file %s: %v", cpuPath, err) 323 } 324 if _, err = f.Write([]byte{'1'}); err != nil { 325 return fmt.Errorf("failed to write '1' to %s: %v", cpuPath, err) 326 } 327 return nil 328 } 329 330 // Disable turns off the CPU by writing 0 to /sys/devices/cpu/cpu{N}/online. 331 func (t Thread) Disable() error { 332 // The core labeled "cpu0" can never be taken offline via this method. 333 // Linux will return EPERM if the user even creates a file at the /sys 334 // path above. 335 if t.processorNumber == 0 { 336 return fmt.Errorf("invalid shutdown operation: cpu0 cannot be disabled") 337 } 338 cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) 339 return ioutil.WriteFile(cpuPath, []byte{'0'}, 0644) 340 } 341 342 // IsVulnerable checks if a CPU is vulnerable to mds. 343 func (t Thread) IsVulnerable() bool { 344 _, ok := t.bugs[mds] 345 return ok 346 } 347 348 // isActive checks if a CPU is active from /sys/devices/system/cpu/cpu{N}/online 349 // If the file does not exist (ioutil returns in error), we assume the CPU is on. 350 func (t Thread) isActive() bool { 351 cpuPath := fmt.Sprintf(cpuOnlineTemplate, t.processorNumber) 352 data, err := ioutil.ReadFile(cpuPath) 353 if err != nil { 354 return true 355 } 356 return len(data) > 0 && data[0] != '0' 357 } 358 359 // SimilarTo checks family/model/bugs fields for equality of two 360 // processors. 361 func (t Thread) SimilarTo(other Thread) bool { 362 if t.vendorID != other.vendorID { 363 return false 364 } 365 366 if other.cpuFamily != t.cpuFamily { 367 return false 368 } 369 370 if other.model != t.model { 371 return false 372 } 373 374 if len(other.bugs) != len(t.bugs) { 375 return false 376 } 377 378 for bug := range t.bugs { 379 if _, ok := other.bugs[bug]; !ok { 380 return false 381 } 382 } 383 return true 384 } 385 386 // parseProcessor grabs the processor field from /proc/cpuinfo output. 387 func parseProcessor(data string) (int64, error) { 388 return parseIntegerResult(data, processorKey) 389 } 390 391 // parseVendorID grabs the vendor_id field from /proc/cpuinfo output. 392 func parseVendorID(data string) (string, error) { 393 return parseRegex(data, vendorIDKey, `[\w\d]+`) 394 } 395 396 // parseCPUFamily grabs the cpu family field from /proc/cpuinfo output. 397 func parseCPUFamily(data string) (int64, error) { 398 return parseIntegerResult(data, cpuFamilyKey) 399 } 400 401 // parseModel grabs the model field from /proc/cpuinfo output. 402 func parseModel(data string) (int64, error) { 403 return parseIntegerResult(data, modelKey) 404 } 405 406 // parsePhysicalID parses the physical id field. 407 func parsePhysicalID(data string) (int64, error) { 408 return parseIntegerResult(data, physicalIDKey) 409 } 410 411 // parseCoreID parses the core id field. 412 func parseCoreID(data string) (int64, error) { 413 return parseIntegerResult(data, coreIDKey) 414 } 415 416 // parseBugs grabs the bugs field from /proc/cpuinfo output. 417 func parseBugs(data string) (map[string]struct{}, error) { 418 result, err := parseRegex(data, bugsKey, `[\d\w\s]*`) 419 if err != nil { 420 return nil, err 421 } 422 bugs := strings.Split(result, " ") 423 ret := make(map[string]struct{}, len(bugs)) 424 for _, bug := range bugs { 425 ret[bug] = struct{}{} 426 } 427 return ret, nil 428 } 429 430 // parseIntegerResult parses fields expecting an integer. 431 func parseIntegerResult(data, key string) (int64, error) { 432 result, err := parseRegex(data, key, `\d+`) 433 if err != nil { 434 return 0, err 435 } 436 return strconv.ParseInt(result, 0, 64) 437 } 438 439 // buildRegex builds a regex for parsing each CPU field. 440 func buildRegex(key, match string) *regexp.Regexp { 441 reg := fmt.Sprintf(`(?m)^%s\s*:\s*(.*)$`, key) 442 return regexp.MustCompile(reg) 443 } 444 445 // parseRegex parses data with key inserted into a standard regex template. 446 func parseRegex(data, key, match string) (string, error) { 447 r := buildRegex(key, match) 448 matches := r.FindStringSubmatch(data) 449 450 if len(matches) < 2 { 451 return "", fmt.Errorf("failed to match key %q: %q", key, data) 452 } 453 return matches[1], nil 454 }