github.com/google/syzkaller@v0.0.0-20240517125934-c0f1611a36d6/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  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/google/syzkaller/pkg/cover"
    17  )
    18  
    19  type linux int
    20  
    21  func (linux) RequiredFiles() []string {
    22  	return []string{
    23  		"/proc/cpuinfo",
    24  		"/proc/modules",
    25  		"/sys/module/*/sections/.text",
    26  		"/sys/module/kvm*/parameters/*",
    27  	}
    28  }
    29  
    30  func (linux) checkFiles() []string {
    31  	return []string{
    32  		"/proc/version",
    33  		"/proc/filesystems",
    34  		"/sys/kernel/security/lsm",
    35  		"/dev/raw-gadget",
    36  	}
    37  }
    38  
    39  func (linux) machineInfos() []machineInfoFunc {
    40  	return []machineInfoFunc{
    41  		linuxReadCPUInfo,
    42  		linuxReadKVMInfo,
    43  	}
    44  }
    45  
    46  func (linux) parseModules(files filesystem) ([]cover.KernelModule, error) {
    47  	var modules []cover.KernelModule
    48  	re := regexp.MustCompile(`(\w+) ([0-9]+) .*(0[x|X][a-fA-F0-9]+)[^\n]*`)
    49  	modulesText, _ := files.ReadFile("/proc/modules")
    50  	for _, match := range re.FindAllSubmatch(modulesText, -1) {
    51  		name := string(match[1])
    52  		modAddr, err := strconv.ParseUint(string(match[3]), 0, 64)
    53  		if err != nil {
    54  			// /proc/modules is broken, bail out.
    55  			return nil, fmt.Errorf("module %v address parsing error: %w", name, err)
    56  		}
    57  		textAddr, err := linuxModuleTextAddr(files, name)
    58  		if err != nil {
    59  			// Module address unavailable, .text is probably 0. Skip this module.
    60  			continue
    61  		}
    62  		modSize, err := strconv.ParseUint(string(match[2]), 0, 64)
    63  		if err != nil {
    64  			// /proc/modules is broken, bail out.
    65  			return nil, fmt.Errorf("module %v size parsing error: %w", name, err)
    66  		}
    67  		offset := modAddr - textAddr
    68  		modules = append(modules, cover.KernelModule{
    69  			Name: name,
    70  			Addr: textAddr,
    71  			Size: modSize - offset,
    72  		})
    73  	}
    74  	return modules, nil
    75  }
    76  
    77  func linuxModuleTextAddr(files filesystem, module string) (uint64, error) {
    78  	data, err := files.ReadFile("/sys/module/" + module + "/sections/.text")
    79  	if err != nil {
    80  		return 0, fmt.Errorf("could not read module %v .text address file: %w", module, err)
    81  	}
    82  	addrString := strings.TrimSpace(string(data))
    83  	addr, err := strconv.ParseUint(addrString, 0, 64)
    84  	if err != nil {
    85  		return 0, fmt.Errorf("address parsing error in %v: %w", module, err)
    86  	}
    87  	return addr, nil
    88  }
    89  
    90  func linuxReadCPUInfo(files filesystem, w io.Writer) (string, error) {
    91  	data, err := files.ReadFile("/proc/cpuinfo")
    92  	if err != nil {
    93  		return "", fmt.Errorf("error reading CPU info:: %w", err)
    94  	}
    95  
    96  	keyIndices := make(map[string]int)
    97  	type keyValues struct {
    98  		key    string
    99  		values []string
   100  	}
   101  	var info []keyValues
   102  	for s := bufio.NewScanner(bytes.NewReader(data)); s.Scan(); {
   103  		splitted := strings.Split(s.Text(), ":")
   104  		if len(splitted) != 2 {
   105  			continue
   106  		}
   107  		key := strings.TrimSpace(splitted[0])
   108  		val := strings.TrimSpace(splitted[1])
   109  		if idx, ok := keyIndices[key]; !ok {
   110  			idx = len(keyIndices)
   111  			keyIndices[key] = idx
   112  			info = append(info, keyValues{key, []string{val}})
   113  		} else {
   114  			info[idx].values = append(info[idx].values, val)
   115  		}
   116  	}
   117  
   118  	for _, kv := range info {
   119  		// It is guaranteed that len(vals) >= 1
   120  		key := kv.key
   121  		vals := kv.values
   122  		if allEqual(vals) {
   123  			fmt.Fprintf(w, "%-20s: %s\n", key, vals[0])
   124  		} else {
   125  			fmt.Fprintf(w, "%-20s: %s\n", key, strings.Join(vals, ", "))
   126  		}
   127  	}
   128  	return "CPU Info", nil
   129  }
   130  
   131  func allEqual(slice []string) bool {
   132  	for i := 1; i < len(slice); i++ {
   133  		if slice[i] != slice[0] {
   134  			return false
   135  		}
   136  	}
   137  	return true
   138  }
   139  
   140  func linuxReadKVMInfo(files filesystem, w io.Writer) (string, error) {
   141  	for _, module := range files.ReadDir("/sys/module") {
   142  		if !strings.HasPrefix(module, "kvm") {
   143  			continue
   144  		}
   145  		paramPath := path.Join("/sys", "module", module, "parameters")
   146  		fmt.Fprintf(w, "/sys/module/%s:\n", module)
   147  		for _, param := range files.ReadDir(paramPath) {
   148  			data, err := files.ReadFile(path.Join(paramPath, param))
   149  			if err != nil {
   150  				return "", fmt.Errorf("error reading KVM info: %w", err)
   151  			}
   152  			fmt.Fprintf(w, "\t%s: %s", param, data)
   153  		}
   154  		w.Write([]byte{'\n'})
   155  	}
   156  	return "KVM", nil
   157  }