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  }