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  }