github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/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 "regexp" 23 "strconv" 24 "strings" 25 ) 26 27 const ( 28 // mds is the only bug we care about. 29 mds = "mds" 30 31 // Constants for parsing /proc/cpuinfo. 32 processorKey = "processor" 33 vendorIDKey = "vendor_id" 34 cpuFamilyKey = "cpu family" 35 modelKey = "model" 36 physicalIDKey = "physical id" 37 coreIDKey = "core id" 38 bugsKey = "bugs" 39 ) 40 41 // CPUSet contains a map of all CPUs on the system, mapped 42 // by Physical ID and CoreIDs. threads with the same 43 // Core and Physical ID are Hyperthread pairs. 44 type CPUSet []*CPU 45 46 // NewCPUSet creates a CPUSet from data read from /proc/cpuinfo. 47 func NewCPUSet(data string) (CPUSet, error) { 48 // Each processor entry should start with the 49 // processor key. Find the beginings of each. 50 r := buildRegex(processorKey) 51 indices := r.FindAllStringIndex(data, -1) 52 53 if len(indices) < 1 { 54 return nil, fmt.Errorf("no cpus found for: %q", data) 55 } 56 57 // Add the ending index for last entry. 58 indices = append(indices, []int{len(data), -1}) 59 60 // Valid cpus are now defined by strings in between 61 // indexes (e.g. data[index[i], index[i+1]]). 62 // There should be len(indicies) - 1 CPUs 63 // since the last index is the end of the string. 64 var set CPUSet 65 // Find each string that represents a CPU. These begin "processor". 66 for i := 1; i < len(indices); i++ { 67 start := indices[i-1][0] 68 end := indices[i][0] 69 // Parse the CPU entry, which should be between start/end. 70 c, err := newCPU(data[start:end]) 71 if err != nil { 72 return nil, err 73 } 74 set = append(set, c) 75 } 76 return set, nil 77 } 78 79 // IsVulnerable checks if this CPUSet is vulnerable to MDS. 80 func (c CPUSet) IsVulnerable() bool { 81 for _, cpu := range c { 82 if cpu.IsVulnerable() { 83 return true 84 } 85 } 86 return false 87 } 88 89 // String implements the String method for CPUSet. 90 func (c CPUSet) String() string { 91 parts := make([]string, len(c)) 92 for i, cpu := range c { 93 parts[i] = cpu.String() 94 } 95 return strings.Join(parts, "\n") 96 } 97 98 // CPU represents pertinent info about a single hyperthread in a pair. 99 type CPU struct { 100 processorNumber int64 // the processor number of this CPU. 101 vendorID string // the vendorID of CPU (e.g. AuthenticAMD). 102 cpuFamily int64 // CPU family number (e.g. 6 for CascadeLake/Skylake). 103 model int64 // CPU model number (e.g. 85 for CascadeLake/Skylake). 104 physicalID int64 // Physical ID of this CPU. 105 coreID int64 // Core ID of this CPU. 106 bugs map[string]struct{} // map of vulnerabilities parsed from the 'bugs' field. 107 } 108 109 func newCPU(data string) (*CPU, error) { 110 processor, err := parseProcessor(data) 111 if err != nil { 112 return nil, err 113 } 114 115 vendorID, err := parseVendorID(data) 116 if err != nil { 117 return nil, err 118 } 119 120 cpuFamily, err := parseCPUFamily(data) 121 if err != nil { 122 return nil, err 123 } 124 125 model, err := parseModel(data) 126 if err != nil { 127 return nil, err 128 } 129 130 physicalID, err := parsePhysicalID(data) 131 if err != nil { 132 return nil, err 133 } 134 135 coreID, err := parseCoreID(data) 136 if err != nil { 137 return nil, err 138 } 139 140 bugs, err := parseBugs(data) 141 if err != nil { 142 return nil, err 143 } 144 145 return &CPU{ 146 processorNumber: processor, 147 vendorID: vendorID, 148 cpuFamily: cpuFamily, 149 model: model, 150 physicalID: physicalID, 151 coreID: coreID, 152 bugs: bugs, 153 }, nil 154 } 155 156 // String implements the String method for CPU. 157 func (t *CPU) String() string { 158 template := `%s: %d 159 %s: %s 160 %s: %d 161 %s: %d 162 %s: %d 163 %s: %d 164 %s: %s 165 ` 166 var bugs []string 167 for bug := range t.bugs { 168 bugs = append(bugs, bug) 169 } 170 171 return fmt.Sprintf(template, 172 processorKey, t.processorNumber, 173 vendorIDKey, t.vendorID, 174 cpuFamilyKey, t.cpuFamily, 175 modelKey, t.model, 176 physicalIDKey, t.physicalID, 177 coreIDKey, t.coreID, 178 bugsKey, strings.Join(bugs, " ")) 179 } 180 181 // IsVulnerable checks if a CPU is vulnerable to mds. 182 func (t *CPU) IsVulnerable() bool { 183 _, ok := t.bugs[mds] 184 return ok 185 } 186 187 // SimilarTo checks family/model/bugs fields for equality of two 188 // processors. 189 func (t *CPU) SimilarTo(other *CPU) bool { 190 if t.vendorID != other.vendorID { 191 return false 192 } 193 194 if other.cpuFamily != t.cpuFamily { 195 return false 196 } 197 198 if other.model != t.model { 199 return false 200 } 201 202 if len(other.bugs) != len(t.bugs) { 203 return false 204 } 205 206 for bug := range t.bugs { 207 if _, ok := other.bugs[bug]; !ok { 208 return false 209 } 210 } 211 return true 212 } 213 214 // parseProcessor grabs the processor field from /proc/cpuinfo output. 215 func parseProcessor(data string) (int64, error) { 216 return parseIntegerResult(data, processorKey) 217 } 218 219 // parseVendorID grabs the vendor_id field from /proc/cpuinfo output. 220 func parseVendorID(data string) (string, error) { 221 return parseRegex(data, vendorIDKey, `[\w\d]+`) 222 } 223 224 // parseCPUFamily grabs the cpu family field from /proc/cpuinfo output. 225 func parseCPUFamily(data string) (int64, error) { 226 return parseIntegerResult(data, cpuFamilyKey) 227 } 228 229 // parseModel grabs the model field from /proc/cpuinfo output. 230 func parseModel(data string) (int64, error) { 231 return parseIntegerResult(data, modelKey) 232 } 233 234 // parsePhysicalID parses the physical id field. 235 func parsePhysicalID(data string) (int64, error) { 236 return parseIntegerResult(data, physicalIDKey) 237 } 238 239 // parseCoreID parses the core id field. 240 func parseCoreID(data string) (int64, error) { 241 return parseIntegerResult(data, coreIDKey) 242 } 243 244 // parseBugs grabs the bugs field from /proc/cpuinfo output. 245 func parseBugs(data string) (map[string]struct{}, error) { 246 result, err := parseRegex(data, bugsKey, `[\d\w\s]*`) 247 if err != nil { 248 return nil, err 249 } 250 bugs := strings.Split(result, " ") 251 ret := make(map[string]struct{}, len(bugs)) 252 for _, bug := range bugs { 253 ret[bug] = struct{}{} 254 } 255 return ret, nil 256 } 257 258 // parseIntegerResult parses fields expecting an integer. 259 func parseIntegerResult(data, key string) (int64, error) { 260 result, err := parseRegex(data, key, `\d+`) 261 if err != nil { 262 return 0, err 263 } 264 return strconv.ParseInt(result, 0, 64) 265 } 266 267 // buildRegex builds a regex for parsing each CPU field. 268 func buildRegex(key string) *regexp.Regexp { 269 reg := fmt.Sprintf(`(?m)^%s\s*:\s*(.*)$`, key) 270 return regexp.MustCompile(reg) 271 } 272 273 // parseRegex parses data with key inserted into a standard regex template. 274 func parseRegex(data, key, match string) (string, error) { 275 r := buildRegex(key) 276 matches := r.FindStringSubmatch(data) 277 278 if len(matches) < 2 { 279 return "", fmt.Errorf("failed to match key %q: %q", key, data) 280 } 281 return matches[1], nil 282 }