github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/pkg/vminfo/linux.go (about)

     1  // Copyright 2024 syzkaller project authors. All rights reserved.
     2  // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
     3  
     4  package vminfo
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"io"
    11  	"path"
    12  	"regexp"
    13  	"sort"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/google/syzkaller/sys/targets"
    18  )
    19  
    20  type linux struct {
    21  	vmType string
    22  }
    23  
    24  func (linux) RequiredFiles() []string {
    25  	return []string{
    26  		"/proc/cpuinfo",
    27  		"/proc/modules",
    28  		"/proc/kallsyms",
    29  		"/sys/module/*/sections/.text",
    30  		"/sys/module/kvm*/parameters/*",
    31  	}
    32  }
    33  
    34  func (linux) CheckFiles() []string {
    35  	return []string{
    36  		"/proc/version",
    37  		"/proc/filesystems",
    38  		"/sys/kernel/security/lsm",
    39  	}
    40  }
    41  
    42  func (linux) machineInfos() []machineInfoFunc {
    43  	return []machineInfoFunc{
    44  		linuxReadCPUInfo,
    45  		linuxReadKVMInfo,
    46  	}
    47  }
    48  
    49  func (linux linux) parseModules(files filesystem) ([]*KernelModule, error) {
    50  	if linux.vmType == targets.GVisor || linux.vmType == targets.Starnix {
    51  		return nil, nil
    52  	}
    53  	var modules []*KernelModule
    54  	re := regexp.MustCompile(`(\w+) ([0-9]+) .*(0[x|X][a-fA-F0-9]+)[^\n]*`)
    55  	modulesText, _ := files.ReadFile("/proc/modules")
    56  	for _, match := range re.FindAllSubmatch(modulesText, -1) {
    57  		name := string(match[1])
    58  		modAddr, err := strconv.ParseUint(string(match[3]), 0, 64)
    59  		if err != nil {
    60  			// /proc/modules is broken, bail out.
    61  			return nil, fmt.Errorf("module %v address parsing error: %w", name, err)
    62  		}
    63  		textAddr, err := linuxModuleTextAddr(files, name)
    64  		if err != nil {
    65  			// Module address unavailable, .text is probably 0. Skip this module.
    66  			continue
    67  		}
    68  		modSize, err := strconv.ParseUint(string(match[2]), 0, 64)
    69  		if err != nil {
    70  			// /proc/modules is broken, bail out.
    71  			return nil, fmt.Errorf("module %v size parsing error: %w", name, err)
    72  		}
    73  		offset := modAddr - textAddr
    74  		modules = append(modules, &KernelModule{
    75  			Name: name,
    76  			Addr: textAddr,
    77  			// The size is wrong as there is overlap in /proc/modules
    78  			// ex. module1['Addr'] + module1['Size'] > module2['Addr']
    79  			// runtime kernel doesn't export .text section size to /sys/module/*/sections/.text
    80  			// so we need to read it from elf
    81  			Size: modSize - offset,
    82  		})
    83  	}
    84  	_stext, _etext, err := linuxParseCoreKernel(files)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	modules = append(modules, &KernelModule{
    89  		Name: "",
    90  		Addr: _stext,
    91  		Size: _etext - _stext,
    92  	})
    93  	sort.Slice(modules, func(i, j int) bool {
    94  		return modules[i].Addr < modules[j].Addr
    95  	})
    96  	return modules, nil
    97  }
    98  
    99  func linuxModuleTextAddr(files filesystem, module string) (uint64, error) {
   100  	data, err := files.ReadFile("/sys/module/" + module + "/sections/.text")
   101  	if err != nil {
   102  		return 0, fmt.Errorf("could not read module %v .text address file: %w", module, err)
   103  	}
   104  	addrString := strings.TrimSpace(string(data))
   105  	addr, err := strconv.ParseUint(addrString, 0, 64)
   106  	if err != nil {
   107  		return 0, fmt.Errorf("address parsing error in %v: %w", module, err)
   108  	}
   109  	return addr, nil
   110  }
   111  
   112  func linuxParseCoreKernel(files filesystem) (uint64, uint64, error) {
   113  	_Text, _ := files.ReadFile("/proc/kallsyms")
   114  	re := regexp.MustCompile(`([a-fA-F0-9]+) T _stext\n`)
   115  	m := re.FindSubmatch(_Text)
   116  	if m == nil {
   117  		return 0, 0, fmt.Errorf("failed to get _stext symbol")
   118  	}
   119  	_stext, err := strconv.ParseUint("0x"+string(m[1]), 0, 64)
   120  	if err != nil {
   121  		return 0, 0, fmt.Errorf("address parsing error in /proc/kallsyms for _stext: %w", err)
   122  	}
   123  	// _etext symbol points to the _next_ section, so it has type of the next section.
   124  	// It can be at least T, D, or R in some cases:
   125  	// https://groups.google.com/g/syzkaller/c/LSx6YIK_Eeo
   126  	re = regexp.MustCompile(`([a-fA-F0-9]+) . _etext\n`)
   127  	m = re.FindSubmatch(_Text)
   128  	if m == nil {
   129  		return 0, 0, fmt.Errorf("failed to get _etext symbol")
   130  	}
   131  	_etext, err := strconv.ParseUint("0x"+string(m[1]), 0, 64)
   132  	if err != nil {
   133  		return 0, 0, fmt.Errorf("address parsing error in /proc/kallsyms for _etext: %w", err)
   134  	}
   135  	return _stext, _etext, nil
   136  }
   137  
   138  func linuxReadCPUInfo(files filesystem, w io.Writer) (string, error) {
   139  	data, err := files.ReadFile("/proc/cpuinfo")
   140  	if err != nil {
   141  		return "", fmt.Errorf("error reading CPU info:: %w", err)
   142  	}
   143  
   144  	keyIndices := make(map[string]int)
   145  	type keyValues struct {
   146  		key    string
   147  		values []string
   148  	}
   149  	var info []keyValues
   150  	for s := bufio.NewScanner(bytes.NewReader(data)); s.Scan(); {
   151  		splitted := strings.Split(s.Text(), ":")
   152  		if len(splitted) != 2 {
   153  			continue
   154  		}
   155  		key := strings.TrimSpace(splitted[0])
   156  		val := strings.TrimSpace(splitted[1])
   157  		if idx, ok := keyIndices[key]; !ok {
   158  			idx = len(keyIndices)
   159  			keyIndices[key] = idx
   160  			info = append(info, keyValues{key, []string{val}})
   161  		} else {
   162  			info[idx].values = append(info[idx].values, val)
   163  		}
   164  	}
   165  
   166  	for _, kv := range info {
   167  		// It is guaranteed that len(vals) >= 1
   168  		key := kv.key
   169  		vals := kv.values
   170  		if allEqual(vals) {
   171  			fmt.Fprintf(w, "%-20s: %s\n", key, vals[0])
   172  		} else {
   173  			fmt.Fprintf(w, "%-20s: %s\n", key, strings.Join(vals, ", "))
   174  		}
   175  	}
   176  	return "CPU Info", nil
   177  }
   178  
   179  func allEqual(slice []string) bool {
   180  	for i := 1; i < len(slice); i++ {
   181  		if slice[i] != slice[0] {
   182  			return false
   183  		}
   184  	}
   185  	return true
   186  }
   187  
   188  func linuxReadKVMInfo(files filesystem, w io.Writer) (string, error) {
   189  	for _, module := range files.ReadDir("/sys/module") {
   190  		if !strings.HasPrefix(module, "kvm") {
   191  			continue
   192  		}
   193  		paramPath := path.Join("/sys", "module", module, "parameters")
   194  		fmt.Fprintf(w, "/sys/module/%s:\n", module)
   195  		for _, param := range files.ReadDir(paramPath) {
   196  			data, err := files.ReadFile(path.Join(paramPath, param))
   197  			if err != nil {
   198  				return "", fmt.Errorf("error reading KVM info: %w", err)
   199  			}
   200  			fmt.Fprintf(w, "\t%s: %s", param, data)
   201  		}
   202  		w.Write([]byte{'\n'})
   203  	}
   204  	return "KVM", nil
   205  }